@softarc/native-federation-orchestrator 4.5.2 → 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 (32) hide show
  1. package/README.md +2 -2
  2. package/fesm2022/@softarc/native-federation-orchestrator.mjs +1310 -257
  3. package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
  4. package/fesm2022/module-federation.mjs +27 -8
  5. package/fesm2022/module-federation.mjs.map +3 -3
  6. package/fesm2022/node.mjs +1307 -254
  7. package/fesm2022/node.mjs.map +4 -4
  8. package/fesm2022/options.mjs +5 -10
  9. package/fesm2022/options.mjs.map +2 -2
  10. package/package.json +2 -2
  11. package/quickstart.mjs +15 -15
  12. package/types/lib/core/1.domain/externals/basis.d.ts +22 -0
  13. package/types/lib/core/1.domain/externals/version.contract.d.ts +1 -0
  14. package/types/lib/core/1.domain/remote-entry/remote-entry.contract.d.ts +2 -0
  15. package/types/lib/core/2.app/config/mode.contract.d.ts +1 -0
  16. package/types/lib/core/2.app/driver-ports/init/drivers.contract.d.ts +2 -0
  17. package/types/lib/core/2.app/driver-ports/init/for-determining-shared-externals.port.d.ts +8 -1
  18. package/types/lib/core/2.app/driver-ports/init/for-marking-pools-for-reelection.port.d.ts +6 -0
  19. package/types/lib/core/2.app/driver-ports/init/for-pooling-shared-externals.port.d.ts +3 -1
  20. package/types/lib/core/2.app/driving-ports/for-shared-chunks-storage.port.d.ts +1 -0
  21. package/types/lib/core/2.app/driving-ports/for-shared-externals-storage.port.d.ts +2 -3
  22. package/types/lib/core/2.app/steps/apply-winner.d.ts +18 -0
  23. package/types/lib/core/2.app/steps/pooling/anchoring.d.ts +69 -0
  24. package/types/lib/core/2.app/steps/pooling/mark-pools-for-reelection.d.ts +5 -0
  25. package/types/lib/core/2.app/steps/pooling/pool-dynamic-externals.d.ts +3 -1
  26. package/types/lib/core/2.app/steps/pooling/pool-graph.d.ts +10 -5
  27. package/types/lib/core/2.app/steps/pooling/pool-shared-externals.d.ts +30 -1
  28. package/types/lib/core/2.app/steps/pooling/pool-views.d.ts +63 -0
  29. package/types/lib/core/2.app/steps/pooling/pool.types.d.ts +30 -1
  30. package/types/lib/core/2.app/steps/pooling/pool.util.d.ts +15 -0
  31. package/types/lib/core/2.app/steps/store-remote-entry.d.ts +15 -7
  32. package/types/lib/testing/pooling/no-tear.d.ts +37 -0
package/fesm2022/node.mjs CHANGED
@@ -1490,16 +1490,21 @@ var createRemoteInfoRepository = (config) => {
1490
1490
  const STORAGE = config.storage("remotes", {});
1491
1491
  if (config.clearStorage) STORAGE.clear();
1492
1492
  const _cache = STORAGE.get() ?? {};
1493
+ let _dirty = false;
1493
1494
  return {
1494
1495
  contains: function(remoteName) {
1495
1496
  return !!_cache[remoteName];
1496
1497
  },
1497
1498
  remove: function(remoteName) {
1498
- delete _cache[remoteName];
1499
+ if (remoteName in _cache) {
1500
+ delete _cache[remoteName];
1501
+ _dirty = true;
1502
+ }
1499
1503
  return this;
1500
1504
  },
1501
1505
  addOrUpdate: function(remoteName, remote) {
1502
1506
  _cache[remoteName] = remote;
1507
+ _dirty = true;
1503
1508
  return this;
1504
1509
  },
1505
1510
  tryGet: function(remoteName) {
@@ -1514,7 +1519,9 @@ var createRemoteInfoRepository = (config) => {
1514
1519
  return _cache;
1515
1520
  },
1516
1521
  commit: function() {
1522
+ if (!_dirty) return this;
1517
1523
  STORAGE.set(_cache);
1524
+ _dirty = false;
1518
1525
  return this;
1519
1526
  }
1520
1527
  };
@@ -1525,14 +1532,19 @@ var createScopedExternalsRepository = (config) => {
1525
1532
  const STORAGE = config.storage("scoped-externals", {});
1526
1533
  if (config.clearStorage) STORAGE.clear();
1527
1534
  const _cache = STORAGE.get() ?? {};
1535
+ let _dirty = false;
1528
1536
  return {
1529
1537
  addExternal: function(remoteName, external, version) {
1530
1538
  if (!_cache[remoteName]) _cache[remoteName] = {};
1531
1539
  _cache[remoteName][external] = version;
1540
+ _dirty = true;
1532
1541
  return this;
1533
1542
  },
1534
1543
  remove: function(remoteName) {
1535
- delete _cache[remoteName];
1544
+ if (remoteName in _cache) {
1545
+ delete _cache[remoteName];
1546
+ _dirty = true;
1547
+ }
1536
1548
  return this;
1537
1549
  },
1538
1550
  getAll: function() {
@@ -1542,7 +1554,9 @@ var createScopedExternalsRepository = (config) => {
1542
1554
  return Optional.of(_cache[remoteName]);
1543
1555
  },
1544
1556
  commit: function() {
1557
+ if (!_dirty) return this;
1545
1558
  STORAGE.set(_cache);
1559
+ _dirty = false;
1546
1560
  return this;
1547
1561
  }
1548
1562
  };
@@ -1559,14 +1573,19 @@ var createSharedExternalsRepository = (config) => {
1559
1573
  { [GLOBAL_SCOPE]: {} }
1560
1574
  );
1561
1575
  if (config.clearStorage) STORAGE.clear();
1562
- const _cache = STORAGE.get();
1563
- let _sawPoolTag = false;
1576
+ const _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
1577
+ let _dirty = false;
1564
1578
  return {
1565
- markPoolTagPresent: function() {
1566
- _sawPoolTag = true;
1567
- },
1568
- hasPoolTag: function() {
1569
- 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;
1570
1589
  },
1571
1590
  getFromScope: function(shareScope) {
1572
1591
  return { ..._cache[shareScope ?? GLOBAL_SCOPE] };
@@ -1574,28 +1593,41 @@ var createSharedExternalsRepository = (config) => {
1574
1593
  addOrUpdate: function(externalName, external, shareScope) {
1575
1594
  if (!_cache[shareScope ?? GLOBAL_SCOPE]) _cache[shareScope ?? GLOBAL_SCOPE] = {};
1576
1595
  _cache[shareScope ?? GLOBAL_SCOPE][externalName] = external;
1596
+ _dirty = true;
1577
1597
  return this;
1578
1598
  },
1579
1599
  getScopes: function(o = { includeGlobal: true }) {
1580
1600
  if (o.includeGlobal) return Object.keys(_cache);
1581
1601
  return Object.keys(_cache).filter((s) => s !== GLOBAL_SCOPE);
1582
1602
  },
1583
- removeFromAllScopes: function(remoteName) {
1603
+ // Batched: one traversal of the graph per init instead of one per overridden remote.
1604
+ removeFromAllScopes: function(remoteNames) {
1605
+ if (remoteNames.size === 0) return;
1584
1606
  Object.values(_cache).forEach((scope) => {
1585
1607
  const removeExternals = [];
1586
1608
  Object.entries(scope).forEach(([name, external]) => {
1587
- const removeVersionIdx = [];
1588
- external.versions.forEach((version, i) => {
1589
- const versionRemoteIDX = version.remotes.findIndex((r) => r.name === remoteName);
1590
- if (~versionRemoteIDX) {
1591
- version.remotes.splice(versionRemoteIDX, 1);
1609
+ let removedCopy = false;
1610
+ for (let i = external.versions.length - 1; i >= 0; i--) {
1611
+ const version = external.versions[i];
1612
+ const remotes = version.remotes;
1613
+ const host = version.host ? remotes[0]?.name : void 0;
1614
+ let keep = 0;
1615
+ for (let r = 0; r < remotes.length; r++) {
1616
+ if (remoteNames.has(remotes[r].name)) continue;
1617
+ remotes[keep++] = remotes[r];
1592
1618
  }
1593
- if (version.remotes.length === 0) removeVersionIdx.push(i);
1594
- });
1595
- if (removeVersionIdx.length > 0) {
1596
- for (let i = removeVersionIdx.length - 1; i >= 0; i--) {
1597
- external.versions.splice(removeVersionIdx[i], 1);
1619
+ if (keep !== remotes.length) {
1620
+ remotes.length = keep;
1621
+ removedCopy = true;
1622
+ _dirty = true;
1623
+ if (host !== void 0 && remoteNames.has(host)) version.host = false;
1624
+ }
1625
+ if (remotes.length === 0) {
1626
+ external.versions.splice(i, 1);
1627
+ _dirty = true;
1598
1628
  }
1629
+ }
1630
+ if (removedCopy) {
1599
1631
  external.dirty = true;
1600
1632
  if (external.versions.length === 0) removeExternals.push(name);
1601
1633
  }
@@ -1619,7 +1651,9 @@ var createSharedExternalsRepository = (config) => {
1619
1651
  return Optional.of(_cache[shareScope ?? GLOBAL_SCOPE]?.[external]);
1620
1652
  },
1621
1653
  commit: function() {
1654
+ if (!_dirty) return this;
1622
1655
  STORAGE.set(_cache);
1656
+ _dirty = false;
1623
1657
  return this;
1624
1658
  }
1625
1659
  };
@@ -1630,17 +1664,29 @@ var createChunkRepository = (config) => {
1630
1664
  const STORAGE = config.storage("shared-chunks", {});
1631
1665
  if (config.clearStorage) STORAGE.clear();
1632
1666
  const _cache = STORAGE.get() ?? {};
1667
+ let _dirty = false;
1633
1668
  return {
1634
1669
  addOrReplace: function(remoteName, bundleName, chunks) {
1635
1670
  if (!_cache[remoteName]) _cache[remoteName] = {};
1636
1671
  _cache[remoteName][bundleName] = chunks;
1672
+ _dirty = true;
1673
+ return this;
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
+ }
1637
1681
  return this;
1638
1682
  },
1639
1683
  tryGet: function(remoteName, bundleName) {
1640
1684
  return Optional.of(_cache[remoteName]?.[bundleName]);
1641
1685
  },
1642
1686
  commit: function() {
1687
+ if (!_dirty) return this;
1643
1688
  STORAGE.set(_cache);
1689
+ _dirty = false;
1644
1690
  return this;
1645
1691
  }
1646
1692
  };
@@ -1830,10 +1876,9 @@ var globalThisStorageEntry = (namespace) => (key, initialValue) => {
1830
1876
  globalThis[namespace] = {};
1831
1877
  }
1832
1878
  const storage = globalThis[namespace];
1833
- if (!storage[key]) storage[key] = initialValue;
1834
1879
  const entry = {
1835
1880
  get() {
1836
- return cloneEntry(key, storage[key]);
1881
+ return cloneEntry(key, key in storage ? storage[key] : initialValue);
1837
1882
  },
1838
1883
  set(value) {
1839
1884
  storage[key] = cloneEntry(key, value);
@@ -1857,6 +1902,7 @@ var createStorageConfig = (override) => ({
1857
1902
  var defaultProfile = {
1858
1903
  latestSharedExternal: false,
1859
1904
  skipInvalidExternalVersions: false,
1905
+ scopeUncoveredEntrypoints: false,
1860
1906
  overrideCachedRemotes: "init-only",
1861
1907
  overrideCachedRemotesIfURLMatches: false
1862
1908
  };
@@ -1903,7 +1949,7 @@ var createInitFlow = ({
1903
1949
  adapters,
1904
1950
  config
1905
1951
  }) => {
1906
- 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) => ({
1907
1953
  config,
1908
1954
  adapters,
1909
1955
  loadRemoteModule
@@ -2022,19 +2068,92 @@ function createGetRemoteEntries(config, ports) {
2022
2068
  }
2023
2069
  }
2024
2070
 
2071
+ // src/lib/core/1.domain/externals/basis.ts
2072
+ var coverage = (remote) => Object.keys(remote.entries).length;
2073
+ function isBetterBasis(candidate, basis) {
2074
+ if (basis.cached !== candidate.cached) return candidate.cached;
2075
+ return coverage(candidate) > coverage(basis);
2076
+ }
2077
+ function addRemoteToVersion(version, remote, isHost = false) {
2078
+ if (isHost) {
2079
+ version.host = true;
2080
+ version.remotes.unshift(remote);
2081
+ return;
2082
+ }
2083
+ const basis = version.remotes[0];
2084
+ if (basis && !version.host && isBetterBasis(remote, basis)) {
2085
+ version.remotes.unshift(remote);
2086
+ return;
2087
+ }
2088
+ version.remotes.push(remote);
2089
+ }
2090
+ function versionDemands(version) {
2091
+ if (version.remotes.length < 2) return version.remotes;
2092
+ const distinct = /* @__PURE__ */ new Map();
2093
+ for (const remote of version.remotes) {
2094
+ const key = `${remote.requiredVersion}|${remote.strictVersion}|${remote.cached}`;
2095
+ if (!distinct.has(key)) distinct.set(key, remote);
2096
+ }
2097
+ return Array.from(distinct.values());
2098
+ }
2099
+ function findVersionForTag(versions, tag) {
2100
+ let scoped;
2101
+ for (const version of versions) {
2102
+ if (version.tag !== tag) continue;
2103
+ if (version.action !== "scope") return version;
2104
+ scoped ??= version;
2105
+ }
2106
+ return scoped;
2107
+ }
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));
2130
+ }
2131
+ function countUncoveredEntrypoints(remote, covered) {
2132
+ let uncovered = 0;
2133
+ for (const entrypoint in remote.entries) {
2134
+ if (!covered.has(entrypoint)) uncovered++;
2135
+ }
2136
+ return uncovered;
2137
+ }
2138
+
2025
2139
  // src/lib/core/2.app/steps/store-remote-entry.ts
2140
+ function createRemoveCachedRemoteEntries(ports) {
2141
+ return (remoteNames) => {
2142
+ if (remoteNames.size === 0) return;
2143
+ for (const remoteName of remoteNames) {
2144
+ ports.remoteInfoRepo.remove(remoteName);
2145
+ ports.scopedExternalsRepo.remove(remoteName);
2146
+ ports.sharedChunksRepo.remove(remoteName);
2147
+ }
2148
+ ports.sharedExternalsRepo.removeFromAllScopes(remoteNames);
2149
+ };
2150
+ }
2026
2151
  function createStoreRemoteEntry(config, ports, logStep) {
2027
2152
  return (remoteEntry, onSharedExternal) => {
2028
- if (remoteEntry?.override) removeCachedRemoteEntry(remoteEntry);
2029
2153
  addRemoteInfoToStorage(remoteEntry);
2030
2154
  addExternalsToStorage(remoteEntry, onSharedExternal);
2031
2155
  addSharedChunksToStorage(remoteEntry);
2032
2156
  };
2033
- function removeCachedRemoteEntry(remoteEntry) {
2034
- ports.remoteInfoRepo.remove(remoteEntry.name);
2035
- ports.scopedExternalsRepo.remove(remoteEntry.name);
2036
- ports.sharedExternalsRepo.removeFromAllScopes(remoteEntry.name);
2037
- }
2038
2157
  function addRemoteInfoToStorage({ name, url, exposes, integrity }) {
2039
2158
  ports.remoteInfoRepo.addOrUpdate(name, {
2040
2159
  scopeUrl: getScope(url),
@@ -2050,7 +2169,6 @@ function createStoreRemoteEntry(config, ports, logStep) {
2050
2169
  const tag = resolveVersion(remoteEntry, external);
2051
2170
  if (tag === null) return;
2052
2171
  if (external.singleton) {
2053
- if (external.pool?.trim()) ports.sharedExternalsRepo.markPoolTagPresent();
2054
2172
  onSharedExternal(remoteEntry, external, sharedExternalContext(remoteEntry, external, tag));
2055
2173
  } else {
2056
2174
  addScopedExternal(remoteEntry, external, tag);
@@ -2134,14 +2252,35 @@ function createStoreRemoteEntry(config, ports, logStep) {
2134
2252
  // src/lib/core/2.app/steps/process-remote-entries.ts
2135
2253
  function createProcessRemoteEntries(config, ports) {
2136
2254
  const storeRemoteEntry = createStoreRemoteEntry(config, ports, 2);
2255
+ const removeCachedRemoteEntries = createRemoveCachedRemoteEntries(ports);
2137
2256
  return (remoteEntries) => {
2138
2257
  try {
2139
- remoteEntries.forEach((remoteEntry) => storeRemoteEntry(remoteEntry, addSharedExternal));
2258
+ const evictPerEntry = evictOverriddenRemotes(remoteEntries);
2259
+ remoteEntries.forEach((remoteEntry) => {
2260
+ if (remoteEntry?.override && evictPerEntry.has(remoteEntry.name)) {
2261
+ removeCachedRemoteEntries(/* @__PURE__ */ new Set([remoteEntry.name]));
2262
+ }
2263
+ storeRemoteEntry(remoteEntry, addSharedExternal);
2264
+ });
2140
2265
  return Promise.resolve(remoteEntries);
2141
2266
  } catch (e) {
2142
2267
  return Promise.reject(e);
2143
2268
  }
2144
2269
  };
2270
+ function evictOverriddenRemotes(remoteEntries) {
2271
+ const batched = /* @__PURE__ */ new Set();
2272
+ const repeated = /* @__PURE__ */ new Set();
2273
+ const seen = /* @__PURE__ */ new Set();
2274
+ for (const remoteEntry of remoteEntries) {
2275
+ if (!remoteEntry) continue;
2276
+ if (seen.has(remoteEntry.name)) repeated.add(remoteEntry.name);
2277
+ seen.add(remoteEntry.name);
2278
+ if (remoteEntry.override) batched.add(remoteEntry.name);
2279
+ }
2280
+ for (const remoteName of repeated) batched.delete(remoteName);
2281
+ removeCachedRemoteEntries(batched);
2282
+ return repeated;
2283
+ }
2145
2284
  function addSharedExternal(remoteEntry, _sharedInfo, {
2146
2285
  tag,
2147
2286
  remote,
@@ -2150,15 +2289,12 @@ function createProcessRemoteEntries(config, ports) {
2150
2289
  assertSameVersionCompatibility,
2151
2290
  commit
2152
2291
  }) {
2153
- const matchingVersion = cached2.versions.find((version) => version.tag === tag);
2292
+ if (scopeType !== "strict") cached2.dirty = true;
2293
+ const matchingVersion = findVersionForTag(cached2.versions, tag);
2154
2294
  if (matchingVersion) {
2155
2295
  assertSameVersionCompatibility(matchingVersion);
2156
- if (!matchingVersion.host && !!remoteEntry?.host) {
2157
- matchingVersion.host = true;
2158
- matchingVersion.remotes.unshift(remote);
2159
- } else matchingVersion.remotes.push(remote);
2296
+ addRemoteToVersion(matchingVersion, remote, !matchingVersion.host && !!remoteEntry?.host);
2160
2297
  } else {
2161
- if (scopeType !== "strict") cached2.dirty = true;
2162
2298
  cached2.versions.push({
2163
2299
  tag,
2164
2300
  action: scopeType === "strict" ? "share" : "skip",
@@ -2170,19 +2306,154 @@ function createProcessRemoteEntries(config, ports) {
2170
2306
  }
2171
2307
  }
2172
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
+
2173
2429
  // src/lib/core/2.app/steps/determine-shared-externals.ts
2174
2430
  function createDetermineSharedExternals(config, ports) {
2431
+ const applyWinner = createApplyWinner(config);
2175
2432
  return () => {
2433
+ const memo = /* @__PURE__ */ new Map();
2434
+ const isCompatible = (tag, requiredVersion) => {
2435
+ const key = `${tag}|${requiredVersion}`;
2436
+ let hit = memo.get(key);
2437
+ if (hit === void 0) {
2438
+ hit = ports.versionCheck.isCompatible(tag, requiredVersion);
2439
+ memo.set(key, hit);
2440
+ }
2441
+ return hit;
2442
+ };
2443
+ const touched = /* @__PURE__ */ new Map();
2176
2444
  for (const shareScope of ports.sharedExternalsRepo.getScopes()) {
2177
2445
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
2178
2446
  try {
2179
- Object.entries(sharedExternals).filter(([_, e]) => e.dirty).forEach(
2180
- ([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(
2181
2450
  name,
2182
- setVersionActions(name, external),
2451
+ setVersionActions(name, external, isCompatible),
2183
2452
  shareScope
2184
- )
2185
- );
2453
+ );
2454
+ elected.add(name);
2455
+ });
2456
+ if (elected.size > 0) touched.set(shareScope, elected);
2186
2457
  } catch (error) {
2187
2458
  config.log.error(
2188
2459
  3,
@@ -2200,22 +2471,25 @@ function createDetermineSharedExternals(config, ports) {
2200
2471
  );
2201
2472
  }
2202
2473
  }
2203
- return Promise.resolve();
2474
+ return Promise.resolve(touched);
2204
2475
  };
2205
- function uncoveredTears(external, winner) {
2206
- const basis = winner.remotes[0].entries;
2476
+ function uncoveredTears(external, winner, accepts) {
2477
+ const basis = versionEntries(winner);
2207
2478
  return external.versions.reduce((sum, v) => {
2208
2479
  if (v === winner) return sum;
2209
- if (!ports.versionCheck.isCompatible(winner.tag, v.remotes[0].requiredVersion)) return sum;
2210
- return sum + v.remotes.reduce((n, r) => n + Object.keys(r.entries).filter((e) => !(e in basis)).length, 0);
2480
+ if (!accepts(v, winner.tag)) return sum;
2481
+ return v.remotes.reduce(
2482
+ (n, r) => r.servedBy ? n : n + countUncoveredEntrypoints(r, basis),
2483
+ sum
2484
+ );
2211
2485
  }, 0);
2212
2486
  }
2213
- function setVersionActions(externalName, external) {
2487
+ function setVersionActions(externalName, external, isCompatible) {
2214
2488
  if (external.versions.length === 1) {
2215
- external.versions[0].action = "share";
2216
- external.dirty = false;
2217
- return external;
2489
+ return applyWinner(externalName, external, external.versions[0], isCompatible);
2218
2490
  }
2491
+ const acceptance = versionAcceptance(external, isCompatible);
2492
+ const { accepts } = acceptance;
2219
2493
  let sharedVersion = external.versions.find((v) => v.host);
2220
2494
  if (!sharedVersion && config.profile.latestSharedExternal) {
2221
2495
  sharedVersion = external.versions[0];
@@ -2223,13 +2497,39 @@ function createDetermineSharedExternals(config, ports) {
2223
2497
  if (!sharedVersion) {
2224
2498
  let leastExtraDownloads = Number.MAX_VALUE;
2225
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);
2226
2517
  external.versions.forEach((vA) => {
2227
- const extraDownloads = external.versions.filter(
2228
- (vB) => !vB.remotes[0].cached && vB.remotes[0].strictVersion && !ports.versionCheck.isCompatible(vA.tag, vB.remotes[0].requiredVersion)
2229
- ).length;
2230
- const tears = uncoveredTears(external, vA);
2231
- if (extraDownloads < leastExtraDownloads || extraDownloads === leastExtraDownloads && tears < leastTears) {
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
+ );
2524
+ if (extraDownloads < leastExtraDownloads) {
2232
2525
  leastExtraDownloads = extraDownloads;
2526
+ leastTears = uncoveredTears(external, vA, accepts);
2527
+ sharedVersion = vA;
2528
+ return;
2529
+ }
2530
+ if (extraDownloads > leastExtraDownloads) return;
2531
+ const tears = uncoveredTears(external, vA, accepts);
2532
+ if (tears < leastTears) {
2233
2533
  leastTears = tears;
2234
2534
  sharedVersion = vA;
2235
2535
  }
@@ -2238,40 +2538,302 @@ function createDetermineSharedExternals(config, ports) {
2238
2538
  if (!sharedVersion) {
2239
2539
  throw new NFError(`[${externalName}] Could not determine shared version!`);
2240
2540
  }
2241
- external.versions.forEach((v) => {
2242
- if (ports.versionCheck.isCompatible(sharedVersion.tag, v.remotes[0].requiredVersion)) {
2243
- v.action = "skip";
2244
- 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
+ }
2245
2580
  }
2246
- if (config.strict.strictExternalCompatibility && v.remotes[0].strictVersion) {
2247
- config.log.error(
2248
- 3,
2249
- `[${v.remotes[0].name}][${externalName}@${v.tag}] Is not compatible with requiredRange '${sharedVersion.remotes[0]?.requiredVersion}' from remote '${sharedVersion.remotes[0].name}'.`
2250
- );
2251
- 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 });
2252
2595
  }
2253
- v.action = v.remotes[0].strictVersion ? "scope" : "skip";
2254
- });
2255
- sharedVersion.action = "share";
2256
- if (config.strict.strictEntryPointCoverage) {
2257
- const basis = sharedVersion.remotes[0].entries;
2258
- external.versions.forEach((v) => {
2259
- if (v.action !== "skip") return;
2260
- const uncovered = v.remotes.some(
2261
- (r) => Object.keys(r.entries).some((entrypoint) => !(entrypoint in basis))
2262
- );
2263
- if (uncovered) {
2264
- v.action = "scope";
2265
- config.log.debug(
2266
- 3,
2267
- `[${externalName}@${v.tag}] Scoped: entrypoints not covered by shared ${externalName}@${sharedVersion.tag}.`
2268
- );
2596
+ }
2597
+ }
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
+ }
2612
+ }
2613
+ }
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
+ }
2628
+ }
2629
+ }
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
+ }
2642
+ }
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);
2683
+ }
2684
+ }
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]);
2704
+ }
2705
+ }
2706
+ }
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]);
2269
2722
  }
2270
- });
2723
+ byMember.set(member.name, accepted);
2724
+ }
2271
2725
  }
2272
- external.dirty = false;
2273
- return external;
2274
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;
2275
2837
  }
2276
2838
 
2277
2839
  // src/lib/core/2.app/steps/pooling/pool-graph.ts
@@ -2314,34 +2876,56 @@ function createDSU() {
2314
2876
  }
2315
2877
  var extNode = (name) => `ext\0${name}`;
2316
2878
  var tagNode = (remote, tag) => `tag\0${remote}\0${tag}`;
2317
- var scopeNode = (scope) => `scope\0${scope}`;
2879
+ var scopeNode = (remote, scope) => `scope\0${remote}\0${scope}`;
2318
2880
  function autoScope(name, useAutoExternalPooling) {
2319
2881
  return useAutoExternalPooling ? SCOPED_PACKAGE.exec(name)?.[1] : void 0;
2320
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
+ }
2321
2892
  function groupByMembership(candidates, log) {
2322
2893
  const dsu = createDSU();
2323
2894
  const tagged = /* @__PURE__ */ new Set();
2324
- 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
+ }
2325
2901
  for (const candidate of candidates) {
2326
- let joined = false;
2327
- if (candidate.scope !== void 0) {
2328
- dsu.union(extNode(candidate.name), scopeNode(candidate.scope));
2329
- joined = true;
2330
- }
2331
2902
  for (const edge of candidate.tags) {
2332
2903
  dsu.union(extNode(candidate.name), tagNode(edge.remote, edge.tag));
2333
2904
  tagged.add(candidate.name);
2334
- 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);
2335
2912
  }
2336
- 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));
2337
2919
  }
2338
2920
  const byComponent = /* @__PURE__ */ new Map();
2339
- for (const entry of poolable) {
2340
- const root = dsu.component(extNode(entry.name));
2341
- (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 });
2342
2925
  }
2343
2926
  const pools = [];
2344
2927
  for (const members of byComponent.values()) {
2928
+ if (!members.some((m) => joined.has(m.name))) continue;
2345
2929
  members.sort((a, b) => a.name.localeCompare(b.name));
2346
2930
  if (members.length < 2) {
2347
2931
  const only = members[0];
@@ -2369,6 +2953,7 @@ function buildPools(sharedExternals, useAutoExternalPooling, log) {
2369
2953
  return tag ? [{ remote: r.name, tag }] : [];
2370
2954
  })
2371
2955
  ),
2956
+ remotes: external.versions.flatMap((v) => v.remotes.map((r) => r.name)),
2372
2957
  value: { name, external }
2373
2958
  })
2374
2959
  );
@@ -2376,28 +2961,131 @@ function buildPools(sharedExternals, useAutoExternalPooling, log) {
2376
2961
  }
2377
2962
 
2378
2963
  // src/lib/core/2.app/steps/pooling/pool.util.ts
2964
+ function lazy(make) {
2965
+ let value;
2966
+ return () => value ??= make();
2967
+ }
2379
2968
  function remotesInPool(members) {
2380
2969
  return [
2381
2970
  ...new Set(members.flatMap((m) => m.external.versions.flatMap((v) => v.remotes.map((r) => r.name))))
2382
2971
  ];
2383
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
+ }
2384
2982
 
2385
2983
  // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
2386
2984
  function islandedRemotes(members) {
2387
- const islanded = /* @__PURE__ */ new Set();
2985
+ const islanded = /* @__PURE__ */ new Map();
2388
2986
  for (const member of members)
2389
2987
  for (const version of member.external.versions)
2390
- 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
+ }
2391
2997
  return islanded;
2392
2998
  }
2393
- function createPoolSharedExternals(config, ports) {
2394
- return () => {
2395
- const { useAutoExternalPooling } = config.feature;
2396
- if (!useAutoExternalPooling && !ports.sharedExternalsRepo.hasPoolTag()) {
2397
- 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
+ });
2398
3050
  }
2399
- for (const scope of ports.sharedExternalsRepo.getScopes()) {
2400
- if (ports.sharedExternalsRepo.scopeType(scope) === "strict") continue;
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
+ }
3079
+ }
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;
2401
3089
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(scope);
2402
3090
  try {
2403
3091
  for (const [poolName, members] of buildPools(
@@ -2405,6 +3093,7 @@ function createPoolSharedExternals(config, ports) {
2405
3093
  useAutoExternalPooling,
2406
3094
  config.log
2407
3095
  )) {
3096
+ if (touchedInScope && !members.some((m) => touchedInScope.has(m.name))) continue;
2408
3097
  poolFamily(poolName, members, scope);
2409
3098
  }
2410
3099
  } catch (error) {
@@ -2425,45 +3114,138 @@ function createPoolSharedExternals(config, ports) {
2425
3114
  const allRemotes = remotesInPool(members);
2426
3115
  if (allRemotes.length < 2) return;
2427
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
+ };
2428
3124
  config.log.debug(
2429
3125
  3,
2430
- `[${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"}}
2431
3127
  ` + members.map((m) => ` - ${m.name}`).join("\n")
2432
3128
  );
2433
- 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) {
2434
3145
  config.log.error(
2435
3146
  3,
2436
- `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${[...islanded].join(", ")}}.`
3147
+ `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${incompatible.join(", ")}}.`
2437
3148
  );
2438
3149
  throw new NFError(`Could not pool '${poolName}' in scope ${scope}.`);
2439
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
+ }
2440
3155
  for (const member of members) {
2441
- const rebuilt = rebuildMember(member, islanded);
2442
- warnIfScopedOnly(poolName, member.name, rebuilt, scope);
3156
+ const rebuilt = rebuildMember(member, islanded, served, basis.get(member.name));
3157
+ warnIfScopedOnly(poolName, member, rebuilt, islanded, scope);
2443
3158
  ports.sharedExternalsRepo.addOrUpdate(member.name, rebuilt, scope);
2444
3159
  }
2445
3160
  }
2446
- 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) {
2447
3211
  if (rebuilt.versions.some((v) => v.action === "share")) return;
2448
- 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;
2449
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;
2450
3218
  config.log.warn(
2451
3219
  3,
2452
- `[${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.`
2453
3221
  );
2454
3222
  }
2455
- function rebuildMember(member, islanded) {
2456
- const entries = member.external.versions.flatMap(
2457
- (v) => v.remotes.map((meta) => ({ remote: meta.name, tag: v.tag, host: v.host, action: v.action, meta }))
2458
- );
2459
- const isScoped = (e) => islanded.has(e.remote) || e.action === "scope";
2460
- let scoped = entries.filter(isScoped);
2461
- let clean = entries.filter((e) => !isScoped(e));
2462
- if (!clean.some((e) => e.action === "share")) {
2463
- scoped = [...scoped, ...clean];
2464
- clean = [];
2465
- }
2466
- 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));
2467
3249
  const shareVersion = shareEntries.length > 0 ? [
2468
3250
  {
2469
3251
  tag: shareEntries[0].tag,
@@ -2472,37 +3254,70 @@ function createPoolSharedExternals(config, ports) {
2472
3254
  remotes: shareEntries.map((e) => e.meta)
2473
3255
  }
2474
3256
  ] : [];
2475
- const skipByTag = /* @__PURE__ */ new Map();
2476
- for (const e of clean) {
2477
- if (e.action !== "skip") continue;
2478
- const version = skipByTag.get(e.tag) ?? {
2479
- tag: e.tag,
2480
- host: e.host,
2481
- action: "skip",
2482
- remotes: []
2483
- };
2484
- version.remotes.push(e.meta);
2485
- skipByTag.set(e.tag, version);
2486
- }
2487
- const scopeByTag = /* @__PURE__ */ new Map();
2488
- for (const e of scoped) {
2489
- const version = scopeByTag.get(e.tag) ?? {
2490
- tag: e.tag,
2491
- host: false,
2492
- action: "scope",
2493
- remotes: []
2494
- };
2495
- version.remotes.push(e.meta);
2496
- scopeByTag.set(e.tag, version);
2497
- }
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
+ };
2498
3271
  return {
2499
3272
  dirty: false,
2500
- 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))
2501
3281
  };
2502
3282
  }
2503
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
+ }
3296
+
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
+ }
2504
3319
 
2505
- // node_modules/.pnpm/@softarc+native-federation@4.3.2_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
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
2506
3321
  var CHUNK_PREFIX = "@nf-internal";
2507
3322
  function toChunkImport(fileName) {
2508
3323
  if (fileName.startsWith("./")) {
@@ -2563,9 +3378,10 @@ function createGenerateImportMap(config, ports) {
2563
3378
  }
2564
3379
  function processshareScope(importMap, shareScope, chunkBundles) {
2565
3380
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
3381
+ const index = buildServedIndex(sharedExternals);
2566
3382
  for (const [externalName, external] of Object.entries(sharedExternals)) {
2567
3383
  let override = void 0;
2568
- let overrideScope = void 0;
3384
+ const cachedBefore = cachedFingerprint(external);
2569
3385
  for (const version of external.versions) {
2570
3386
  if (version.action === "scope") {
2571
3387
  for (const remote of version.remotes) {
@@ -2581,37 +3397,53 @@ function createGenerateImportMap(config, ports) {
2581
3397
  continue;
2582
3398
  }
2583
3399
  version.remotes[0].cached = true;
2584
- let source = version.remotes[0];
2585
- let sourceScope = getScope2(shareScope, source.name, externalName);
2586
- if (version.action === "share") {
2587
- registerBundleChunks(chunkBundles, source.name, source.bundle);
2588
- } else if (version.action === "skip") {
3400
+ let serving = version;
3401
+ if (version.action === "skip") {
2589
3402
  if (!override) {
2590
3403
  override = findOverride(external, shareScope, externalName) ?? "NOT_AVAILABLE";
2591
3404
  }
2592
3405
  if (override !== "NOT_AVAILABLE") {
2593
- if (!overrideScope)
2594
- overrideScope = getScope2(shareScope, override.remotes[0].name, externalName);
2595
- source = override.remotes[0];
2596
- sourceScope = overrideScope;
2597
- override.remotes[0].cached = true;
3406
+ serving = override;
2598
3407
  version.remotes[0].cached = false;
2599
3408
  }
2600
3409
  }
2601
- const mappings = Object.entries(source.entries).map(([packageName, file]) => ({
2602
- packageName,
2603
- url: join(sourceScope, file),
2604
- name: source.name,
2605
- file
2606
- }));
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
+ }
2607
3425
  version.remotes.forEach((r) => {
2608
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
+ }
2609
3441
  for (const m of mappings) {
2610
3442
  addToScope(importMap, rScope, { [m.packageName]: m.url });
2611
3443
  }
2612
3444
  for (const [packageName, file] of Object.entries(r.entries)) {
2613
- if (packageName in source.entries) continue;
2614
- if (config.strict.strictEntryPointCoverage) {
3445
+ if (provided.has(packageName)) continue;
3446
+ if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
2615
3447
  warnUncoveredEntrypoint(shareScope, externalName, r.name, packageName);
2616
3448
  continue;
2617
3449
  }
@@ -2625,9 +3457,14 @@ function createGenerateImportMap(config, ports) {
2625
3457
  addIntegrity(importMap, m.url, m.name, m.file);
2626
3458
  }
2627
3459
  }
2628
- ports.sharedExternalsRepo.addOrUpdate(externalName, external, shareScope);
3460
+ if (cachedFingerprint(external) !== cachedBefore) {
3461
+ ports.sharedExternalsRepo.addOrUpdate(externalName, external, shareScope);
3462
+ }
2629
3463
  }
2630
3464
  }
3465
+ function cachedFingerprint(external) {
3466
+ return external.versions.map((v) => v.remotes.map((r) => r.cached ? 1 : 0).join("")).join("|");
3467
+ }
2631
3468
  function findOverride(external, shareScope, externalName) {
2632
3469
  const sharedVersions = external.versions.filter((v) => v.action === "share");
2633
3470
  const scopedExternalName = `${shareScope}.${externalName}`;
@@ -2656,10 +3493,68 @@ function createGenerateImportMap(config, ports) {
2656
3493
  }
2657
3494
  config.log.warn(4, `ShareScope external ${scopedExternalName} has multiple shared versions.`);
2658
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
+ }
2659
3547
  function addGlobalSharedExternals(importMap, chunkBundles) {
2660
3548
  const sharedExternals = ports.sharedExternalsRepo.getFromScope();
3549
+ const index = buildServedIndex(sharedExternals);
3550
+ const served = [];
3551
+ const cachedBefore = /* @__PURE__ */ new Map();
2661
3552
  for (const [externalName, external] of Object.entries(sharedExternals)) {
3553
+ cachedBefore.set(externalName, cachedFingerprint(external));
2662
3554
  for (const version of external.versions) {
3555
+ if (index && version.action !== "scope") {
3556
+ collectServed(served, index, GLOBAL_SCOPE, externalName, version);
3557
+ }
2663
3558
  if (version.action === "skip") continue;
2664
3559
  if (version.action === "scope") {
2665
3560
  for (const remote of version.remotes) {
@@ -2678,39 +3573,64 @@ function createGenerateImportMap(config, ports) {
2678
3573
  notifyDuplicateGlobalExternal(externalName);
2679
3574
  continue;
2680
3575
  }
2681
- const scope = getScope2(GLOBAL_SCOPE, version.remotes[0].name, externalName);
2682
- for (const [packageName, file] of Object.entries(version.remotes[0].entries)) {
2683
- const url = join(scope, file);
2684
- addToGlobal(importMap, { [packageName]: url });
2685
- addIntegrity(importMap, url, version.remotes[0].name, file);
2686
- }
2687
- registerBundleChunks(chunkBundles, version.remotes[0].name, version.remotes[0].bundle);
2688
- version.remotes[0].cached = true;
3576
+ mergeVersionEntries(importMap, chunkBundles, externalName, version);
2689
3577
  }
2690
3578
  for (const version of external.versions) {
2691
3579
  if (version.action !== "skip") continue;
2692
- const remote = version.remotes[0];
2693
- for (const [packageName, file] of Object.entries(remote.entries)) {
2694
- if (importMap.imports[packageName]) continue;
2695
- if (config.strict.strictEntryPointCoverage) {
2696
- warnUncoveredEntrypoint(GLOBAL_SCOPE, externalName, remote.name, packageName);
2697
- continue;
2698
- }
2699
- const scope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
2700
- const url = join(scope, file);
2701
- addToGlobal(importMap, { [packageName]: url });
2702
- addIntegrity(importMap, url, remote.name, file);
2703
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
2704
- remote.cached = true;
2705
- }
3580
+ selfFillUncovered(importMap, chunkBundles, externalName, version.remotes);
3581
+ }
3582
+ }
3583
+ flushServed(importMap, chunkBundles, served);
3584
+ for (const [externalName, external] of Object.entries(sharedExternals)) {
3585
+ if (cachedFingerprint(external) !== cachedBefore.get(externalName)) {
3586
+ ports.sharedExternalsRepo.addOrUpdate(externalName, external);
2706
3587
  }
2707
- ports.sharedExternalsRepo.addOrUpdate(externalName, external);
2708
3588
  }
2709
3589
  return importMap;
2710
3590
  }
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;
3616
+ for (const [packageName, file] of Object.entries(remote.entries)) {
3617
+ if (importMap.imports[packageName]) continue;
3618
+ if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
3619
+ warnUncoveredEntrypoint(GLOBAL_SCOPE, externalName, remote.name, packageName);
3620
+ continue;
3621
+ }
3622
+ const scope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
3623
+ const url = join(scope, file);
3624
+ addToGlobal(importMap, { [packageName]: url });
3625
+ addIntegrity(importMap, url, remote.name, file);
3626
+ registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3627
+ remote.cached = true;
3628
+ }
3629
+ }
3630
+ }
2711
3631
  function warnUncoveredEntrypoint(scope, externalName, remoteName, packageName) {
2712
3632
  const msg = `[${scope}][${externalName}][${remoteName}] Entrypoint '${packageName}' is not covered by the shared version.`;
2713
- if (config.strict.strictImportMap) {
3633
+ if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
2714
3634
  config.log.error(4, msg);
2715
3635
  throw new NFError("Could not create ImportMap.");
2716
3636
  }
@@ -2899,18 +3819,24 @@ function createGetRemoteEntry(config, ports) {
2899
3819
  // src/lib/core/2.app/steps/update-cache.ts
2900
3820
  function createUpdateCache(config, ports) {
2901
3821
  const storeRemoteEntry = createStoreRemoteEntry(config, ports, 8);
3822
+ const removeCachedRemoteEntries = createRemoveCachedRemoteEntries(ports);
2902
3823
  return (remoteEntry) => {
2903
3824
  try {
2904
3825
  const actions = {};
3826
+ if (remoteEntry?.override) removeCachedRemoteEntries(/* @__PURE__ */ new Set([remoteEntry.name]));
2905
3827
  storeRemoteEntry(remoteEntry, (entry, external, ctx) => {
2906
- const { action, sharedVersion } = resolveSharedExternal(entry, external, ctx);
3828
+ const { action, provided, sameVersion } = resolveSharedExternal(entry, external, ctx);
2907
3829
  actions[external.packageName] = { action };
2908
- if (action === "skip" && external.shareScope && sharedVersion?.remotes[0]?.entries) {
2909
- actions[external.packageName].override = resolveOverrideEntries(
2910
- entry,
2911
- external,
2912
- sharedVersion
2913
- );
3830
+ if (action === "skip" && provided) {
3831
+ actions[external.packageName].covered = Array.from(provided.keys());
3832
+ if (sameVersion) actions[external.packageName].sameVersion = true;
3833
+ if (external.shareScope) {
3834
+ actions[external.packageName].override = resolveOverrideEntries(
3835
+ entry,
3836
+ external,
3837
+ provided
3838
+ );
3839
+ }
2914
3840
  }
2915
3841
  });
2916
3842
  return Promise.resolve({ entry: remoteEntry, actions });
@@ -2938,78 +3864,188 @@ function createUpdateCache(config, ports) {
2938
3864
  }
2939
3865
  config.log.warn(8, errorMsg);
2940
3866
  }
2941
- if (action === "skip" && config.strict.strictEntryPointCoverage && sharedVersion && Object.keys(remote.entries).some((e) => !(e in sharedVersion.remotes[0].entries))) {
2942
- action = "scope";
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);
3871
+ if (uncovered.length > 0) {
3872
+ const msg = `[${sharedInfo.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}][${sharedInfo.packageName}] Entrypoints not covered by the shared version: ${uncovered.join(", ")}.`;
3873
+ if (config.strict.strictEntryPointCoverage) {
3874
+ config.log.error(8, msg);
3875
+ throw new NFError(`Could not process remote '${remoteEntry.name}'`);
3876
+ }
3877
+ if (config.profile.scopeUncoveredEntrypoints) {
3878
+ config.log.debug(8, msg);
3879
+ action = "scope";
3880
+ }
3881
+ }
2943
3882
  }
2944
- const matchingVersion = cached2.versions.find((cached3) => cached3.tag === tag);
2945
- if (matchingVersion) {
3883
+ const matchingVersion = findVersionForTag(cached2.versions, tag);
3884
+ if (action === "scope") {
3885
+ remote.cached = true;
3886
+ const scoped = cached2.versions.find((v) => v.tag === tag && v.action === "scope");
3887
+ if (scoped) scoped.remotes.push(remote);
3888
+ else cached2.versions.push({ tag, action, host: false, remotes: [remote] });
3889
+ } else if (matchingVersion) {
2946
3890
  assertSameVersionCompatibility(matchingVersion);
2947
- matchingVersion.remotes.push(remote);
3891
+ addRemoteToVersion(matchingVersion, remote);
2948
3892
  } else {
2949
3893
  if (!sharedVersion) action = "share";
2950
3894
  remote.cached = action !== "skip";
2951
3895
  cached2.versions.push({ tag, action, host: false, remotes: [remote] });
2952
3896
  }
2953
3897
  commit();
2954
- return { action, sharedVersion };
2955
- }
2956
- function resolveOverrideEntries(remoteEntry, external, sharedVersion) {
2957
- return ports.remoteInfoRepo.tryGet(sharedVersion.remotes[0].name).map(
2958
- (remote) => Object.fromEntries(
2959
- Object.entries(sharedVersion.remotes[0].entries).map(([packageName, file]) => [
2960
- packageName,
2961
- join(remote.scopeUrl, file)
2962
- ])
2963
- )
2964
- ).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(() => {
2965
3913
  config.log.error(
2966
3914
  8,
2967
3915
  `[${external.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}][${external.packageName}@${external.version}][override] Remote name not found in cache.`
2968
3916
  );
2969
- return new NFError(
2970
- `Could not find override url from remote ${sharedVersion.remotes[0].name}`
2971
- );
3917
+ return new NFError(`Could not find override url from remote ${providerName}`);
2972
3918
  });
2973
3919
  }
2974
3920
  }
2975
3921
 
2976
3922
  // src/lib/core/2.app/steps/pooling/pool-dynamic-externals.ts
2977
- function createPoolDynamicExternals(config) {
3923
+ function createPoolDynamicExternals(config, ports) {
2978
3924
  return ({ entry, actions }) => {
2979
3925
  const { useAutoExternalPooling } = config.feature;
2980
- if (!useAutoExternalPooling && !(entry.shared ?? []).some((e) => e.pool?.trim())) {
2981
- return Promise.resolve({ entry, actions });
2982
- }
2983
- const byScope = /* @__PURE__ */ new Map();
3926
+ const declared = /* @__PURE__ */ new Map();
2984
3927
  for (const external of entry.shared ?? []) {
2985
3928
  const name = external.packageName;
2986
3929
  if (!external.singleton || !actions[name]) continue;
2987
3930
  if (external.shareScope === STRICT_SCOPE) continue;
2988
- const tag = external.pool?.trim();
2989
3931
  const shareScope = external.shareScope ?? GLOBAL_SCOPE;
2990
- const candidates = byScope.get(shareScope) ?? [];
2991
- candidates.push({
2992
- name,
2993
- scope: autoScope(name, useAutoExternalPooling),
2994
- tags: tag ? [{ remote: entry.name, tag }] : [],
2995
- value: name
2996
- });
2997
- byScope.set(shareScope, candidates);
3932
+ let names = declared.get(shareScope);
3933
+ if (!names) declared.set(shareScope, names = /* @__PURE__ */ new Set());
3934
+ names.add(name);
2998
3935
  }
3936
+ if (declared.size === 0) return Promise.resolve({ entry, actions });
2999
3937
  const scope = (name) => {
3000
3938
  actions[name].action = "scope";
3001
3939
  delete actions[name].override;
3002
3940
  };
3003
- for (const candidates of byScope.values()) {
3004
- for (const members of groupByMembership(candidates).values()) {
3005
- const memberActions = members.map((name) => actions[name].action);
3006
- if (memberActions.includes("scope")) {
3007
- 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;
3008
3963
  }
3964
+ redirect(entry.name, anchor, pool, asked.view, actions, shareScope);
3009
3965
  }
3010
3966
  }
3011
3967
  return Promise.resolve({ entry, actions });
3012
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
+ }
3013
4049
  }
3014
4050
 
3015
4051
  // src/lib/core/2.app/steps/convert-to-import-map.ts
@@ -3047,22 +4083,25 @@ function createConvertToImportMap(config, ports) {
3047
4083
  return;
3048
4084
  }
3049
4085
  if (actions[external.packageName].action === "skip") {
3050
- const override = actions[external.packageName].override;
3051
- if (!external.shareScope) return;
4086
+ const { override, covered, sameVersion } = actions[external.packageName];
3052
4087
  if (override) {
3053
4088
  Object.entries(override).forEach(([packageName, url]) => {
3054
4089
  addToScopes(remoteEntryScope, packageName, url, importMap);
3055
4090
  });
3056
- Object.entries(external.entries).forEach(([packageName, fileName]) => {
3057
- if (packageName in override) return;
3058
- if (config.strict.strictEntryPointCoverage) {
3059
- warnUncoveredEntrypoint(remoteEntry.name, external.packageName, packageName);
3060
- return;
3061
- }
3062
- const url = join(remoteEntryScope, fileName);
3063
- addToScopes(remoteEntryScope, packageName, url, importMap);
3064
- addIntegrity(importMap, url, integrityMap, fileName);
3065
- });
4091
+ serveUncovered(
4092
+ remoteEntry,
4093
+ external,
4094
+ covered ?? Object.keys(override),
4095
+ remoteEntryScope,
4096
+ importMap,
4097
+ sameVersion
4098
+ );
4099
+ return;
4100
+ }
4101
+ if (!external.shareScope) {
4102
+ if (covered) {
4103
+ serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap, sameVersion);
4104
+ }
3066
4105
  return;
3067
4106
  }
3068
4107
  log.error(
@@ -3129,9 +4168,22 @@ function createConvertToImportMap(config, ports) {
3129
4168
  });
3130
4169
  return importMap;
3131
4170
  }
4171
+ function serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap, sameVersion = false) {
4172
+ const provided = new Set(covered);
4173
+ Object.entries(external.entries).forEach(([packageName, fileName]) => {
4174
+ if (provided.has(packageName)) return;
4175
+ if (!sameVersion && (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints)) {
4176
+ warnUncoveredEntrypoint(remoteEntry.name, external.packageName, packageName);
4177
+ return;
4178
+ }
4179
+ const url = join(remoteEntryScope, fileName);
4180
+ addToScopes(remoteEntryScope, packageName, url, importMap);
4181
+ addIntegrity(importMap, url, remoteEntry.integrity, fileName);
4182
+ });
4183
+ }
3132
4184
  function warnUncoveredEntrypoint(remoteName, externalName, packageName) {
3133
4185
  const msg = `[${remoteName}][${externalName}] Entrypoint '${packageName}' is not covered by the override.`;
3134
- if (config.strict.strictImportMap) {
4186
+ if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
3135
4187
  log.error(9, msg);
3136
4188
  throw new NFError("Could not create ImportMap.");
3137
4189
  }
@@ -3152,6 +4204,7 @@ var createInitDrivers = ({
3152
4204
  }) => ({
3153
4205
  getRemoteEntries: createGetRemoteEntries(config, adapters),
3154
4206
  processRemoteEntries: createProcessRemoteEntries(config, adapters),
4207
+ markPoolsForReelection: createMarkPoolsForReelection(config, adapters),
3155
4208
  determineSharedExternals: createDetermineSharedExternals(config, adapters),
3156
4209
  poolSharedExternals: createPoolSharedExternals(config, adapters),
3157
4210
  generateImportMap: createGenerateImportMap(config, adapters),
@@ -3159,7 +4212,7 @@ var createInitDrivers = ({
3159
4212
  exposeModuleLoader: createExposeModuleLoader(config, adapters),
3160
4213
  getRemoteEntry: createGetRemoteEntry(config, adapters),
3161
4214
  updateCache: createUpdateCache(config, adapters),
3162
- poolDynamicExternals: createPoolDynamicExternals(config),
4215
+ poolDynamicExternals: createPoolDynamicExternals(config, adapters),
3163
4216
  convertToImportMap: createConvertToImportMap(config, adapters)
3164
4217
  });
3165
4218
  var INIT_FLOW_FACTORY = ({