@softarc/native-federation-orchestrator 4.5.2 → 4.5.3

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.
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,8 +1573,9 @@ var createSharedExternalsRepository = (config) => {
1559
1573
  { [GLOBAL_SCOPE]: {} }
1560
1574
  );
1561
1575
  if (config.clearStorage) STORAGE.clear();
1562
- const _cache = STORAGE.get();
1576
+ const _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
1563
1577
  let _sawPoolTag = false;
1578
+ let _dirty = false;
1564
1579
  return {
1565
1580
  markPoolTagPresent: function() {
1566
1581
  _sawPoolTag = true;
@@ -1574,28 +1589,38 @@ var createSharedExternalsRepository = (config) => {
1574
1589
  addOrUpdate: function(externalName, external, shareScope) {
1575
1590
  if (!_cache[shareScope ?? GLOBAL_SCOPE]) _cache[shareScope ?? GLOBAL_SCOPE] = {};
1576
1591
  _cache[shareScope ?? GLOBAL_SCOPE][externalName] = external;
1592
+ _dirty = true;
1577
1593
  return this;
1578
1594
  },
1579
1595
  getScopes: function(o = { includeGlobal: true }) {
1580
1596
  if (o.includeGlobal) return Object.keys(_cache);
1581
1597
  return Object.keys(_cache).filter((s) => s !== GLOBAL_SCOPE);
1582
1598
  },
1583
- removeFromAllScopes: function(remoteName) {
1599
+ // Batched: one traversal of the graph per init instead of one per overridden remote.
1600
+ removeFromAllScopes: function(remoteNames) {
1601
+ if (remoteNames.size === 0) return;
1584
1602
  Object.values(_cache).forEach((scope) => {
1585
1603
  const removeExternals = [];
1586
1604
  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);
1605
+ let removedVersion = false;
1606
+ for (let i = external.versions.length - 1; i >= 0; i--) {
1607
+ const remotes = external.versions[i].remotes;
1608
+ let keep = 0;
1609
+ for (let r = 0; r < remotes.length; r++) {
1610
+ if (remoteNames.has(remotes[r].name)) continue;
1611
+ remotes[keep++] = remotes[r];
1592
1612
  }
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);
1613
+ if (keep !== remotes.length) {
1614
+ remotes.length = keep;
1615
+ _dirty = true;
1598
1616
  }
1617
+ if (remotes.length === 0) {
1618
+ external.versions.splice(i, 1);
1619
+ removedVersion = true;
1620
+ _dirty = true;
1621
+ }
1622
+ }
1623
+ if (removedVersion) {
1599
1624
  external.dirty = true;
1600
1625
  if (external.versions.length === 0) removeExternals.push(name);
1601
1626
  }
@@ -1619,7 +1644,9 @@ var createSharedExternalsRepository = (config) => {
1619
1644
  return Optional.of(_cache[shareScope ?? GLOBAL_SCOPE]?.[external]);
1620
1645
  },
1621
1646
  commit: function() {
1647
+ if (!_dirty) return this;
1622
1648
  STORAGE.set(_cache);
1649
+ _dirty = false;
1623
1650
  return this;
1624
1651
  }
1625
1652
  };
@@ -1630,17 +1657,21 @@ var createChunkRepository = (config) => {
1630
1657
  const STORAGE = config.storage("shared-chunks", {});
1631
1658
  if (config.clearStorage) STORAGE.clear();
1632
1659
  const _cache = STORAGE.get() ?? {};
1660
+ let _dirty = false;
1633
1661
  return {
1634
1662
  addOrReplace: function(remoteName, bundleName, chunks) {
1635
1663
  if (!_cache[remoteName]) _cache[remoteName] = {};
1636
1664
  _cache[remoteName][bundleName] = chunks;
1665
+ _dirty = true;
1637
1666
  return this;
1638
1667
  },
1639
1668
  tryGet: function(remoteName, bundleName) {
1640
1669
  return Optional.of(_cache[remoteName]?.[bundleName]);
1641
1670
  },
1642
1671
  commit: function() {
1672
+ if (!_dirty) return this;
1643
1673
  STORAGE.set(_cache);
1674
+ _dirty = false;
1644
1675
  return this;
1645
1676
  }
1646
1677
  };
@@ -1830,10 +1861,9 @@ var globalThisStorageEntry = (namespace) => (key, initialValue) => {
1830
1861
  globalThis[namespace] = {};
1831
1862
  }
1832
1863
  const storage = globalThis[namespace];
1833
- if (!storage[key]) storage[key] = initialValue;
1834
1864
  const entry = {
1835
1865
  get() {
1836
- return cloneEntry(key, storage[key]);
1866
+ return cloneEntry(key, key in storage ? storage[key] : initialValue);
1837
1867
  },
1838
1868
  set(value) {
1839
1869
  storage[key] = cloneEntry(key, value);
@@ -1857,6 +1887,7 @@ var createStorageConfig = (override) => ({
1857
1887
  var defaultProfile = {
1858
1888
  latestSharedExternal: false,
1859
1889
  skipInvalidExternalVersions: false,
1890
+ scopeUncoveredEntrypoints: false,
1860
1891
  overrideCachedRemotes: "init-only",
1861
1892
  overrideCachedRemotesIfURLMatches: false
1862
1893
  };
@@ -2022,19 +2053,71 @@ function createGetRemoteEntries(config, ports) {
2022
2053
  }
2023
2054
  }
2024
2055
 
2056
+ // src/lib/core/1.domain/externals/basis.ts
2057
+ var coverage = (remote) => Object.keys(remote.entries).length;
2058
+ function isBetterBasis(candidate, basis) {
2059
+ if (basis.cached !== candidate.cached) return candidate.cached;
2060
+ return coverage(candidate) > coverage(basis);
2061
+ }
2062
+ function addRemoteToVersion(version, remote, isHost = false) {
2063
+ if (isHost) {
2064
+ version.host = true;
2065
+ version.remotes.unshift(remote);
2066
+ return;
2067
+ }
2068
+ const basis = version.remotes[0];
2069
+ if (basis && !version.host && isBetterBasis(remote, basis)) {
2070
+ version.remotes.unshift(remote);
2071
+ return;
2072
+ }
2073
+ version.remotes.push(remote);
2074
+ }
2075
+ function versionDemands(version) {
2076
+ if (version.remotes.length < 2) return version.remotes;
2077
+ const distinct = /* @__PURE__ */ new Map();
2078
+ for (const remote of version.remotes) {
2079
+ const key = `${remote.requiredVersion}|${remote.strictVersion}|${remote.cached}`;
2080
+ if (!distinct.has(key)) distinct.set(key, remote);
2081
+ }
2082
+ return Array.from(distinct.values());
2083
+ }
2084
+ function findVersionForTag(versions, tag) {
2085
+ let scoped;
2086
+ for (const version of versions) {
2087
+ if (version.tag !== tag) continue;
2088
+ if (version.action !== "scope") return version;
2089
+ scoped ??= version;
2090
+ }
2091
+ return scoped;
2092
+ }
2093
+ function uncoveredEntrypoints(remote, basis) {
2094
+ return Object.keys(remote.entries).filter((entrypoint) => !(entrypoint in basis));
2095
+ }
2096
+ function countUncoveredEntrypoints(remote, basis) {
2097
+ let uncovered = 0;
2098
+ for (const entrypoint in remote.entries) {
2099
+ if (!(entrypoint in basis)) uncovered++;
2100
+ }
2101
+ return uncovered;
2102
+ }
2103
+
2025
2104
  // src/lib/core/2.app/steps/store-remote-entry.ts
2105
+ function createRemoveCachedRemoteEntries(ports) {
2106
+ return (remoteNames) => {
2107
+ if (remoteNames.size === 0) return;
2108
+ for (const remoteName of remoteNames) {
2109
+ ports.remoteInfoRepo.remove(remoteName);
2110
+ ports.scopedExternalsRepo.remove(remoteName);
2111
+ }
2112
+ ports.sharedExternalsRepo.removeFromAllScopes(remoteNames);
2113
+ };
2114
+ }
2026
2115
  function createStoreRemoteEntry(config, ports, logStep) {
2027
2116
  return (remoteEntry, onSharedExternal) => {
2028
- if (remoteEntry?.override) removeCachedRemoteEntry(remoteEntry);
2029
2117
  addRemoteInfoToStorage(remoteEntry);
2030
2118
  addExternalsToStorage(remoteEntry, onSharedExternal);
2031
2119
  addSharedChunksToStorage(remoteEntry);
2032
2120
  };
2033
- function removeCachedRemoteEntry(remoteEntry) {
2034
- ports.remoteInfoRepo.remove(remoteEntry.name);
2035
- ports.scopedExternalsRepo.remove(remoteEntry.name);
2036
- ports.sharedExternalsRepo.removeFromAllScopes(remoteEntry.name);
2037
- }
2038
2121
  function addRemoteInfoToStorage({ name, url, exposes, integrity }) {
2039
2122
  ports.remoteInfoRepo.addOrUpdate(name, {
2040
2123
  scopeUrl: getScope(url),
@@ -2134,14 +2217,35 @@ function createStoreRemoteEntry(config, ports, logStep) {
2134
2217
  // src/lib/core/2.app/steps/process-remote-entries.ts
2135
2218
  function createProcessRemoteEntries(config, ports) {
2136
2219
  const storeRemoteEntry = createStoreRemoteEntry(config, ports, 2);
2220
+ const removeCachedRemoteEntries = createRemoveCachedRemoteEntries(ports);
2137
2221
  return (remoteEntries) => {
2138
2222
  try {
2139
- remoteEntries.forEach((remoteEntry) => storeRemoteEntry(remoteEntry, addSharedExternal));
2223
+ const evictPerEntry = evictOverriddenRemotes(remoteEntries);
2224
+ remoteEntries.forEach((remoteEntry) => {
2225
+ if (remoteEntry?.override && evictPerEntry.has(remoteEntry.name)) {
2226
+ removeCachedRemoteEntries(/* @__PURE__ */ new Set([remoteEntry.name]));
2227
+ }
2228
+ storeRemoteEntry(remoteEntry, addSharedExternal);
2229
+ });
2140
2230
  return Promise.resolve(remoteEntries);
2141
2231
  } catch (e) {
2142
2232
  return Promise.reject(e);
2143
2233
  }
2144
2234
  };
2235
+ function evictOverriddenRemotes(remoteEntries) {
2236
+ const batched = /* @__PURE__ */ new Set();
2237
+ const repeated = /* @__PURE__ */ new Set();
2238
+ const seen = /* @__PURE__ */ new Set();
2239
+ for (const remoteEntry of remoteEntries) {
2240
+ if (!remoteEntry) continue;
2241
+ if (seen.has(remoteEntry.name)) repeated.add(remoteEntry.name);
2242
+ seen.add(remoteEntry.name);
2243
+ if (remoteEntry.override) batched.add(remoteEntry.name);
2244
+ }
2245
+ for (const remoteName of repeated) batched.delete(remoteName);
2246
+ removeCachedRemoteEntries(batched);
2247
+ return repeated;
2248
+ }
2145
2249
  function addSharedExternal(remoteEntry, _sharedInfo, {
2146
2250
  tag,
2147
2251
  remote,
@@ -2150,15 +2254,12 @@ function createProcessRemoteEntries(config, ports) {
2150
2254
  assertSameVersionCompatibility,
2151
2255
  commit
2152
2256
  }) {
2153
- const matchingVersion = cached2.versions.find((version) => version.tag === tag);
2257
+ if (scopeType !== "strict") cached2.dirty = true;
2258
+ const matchingVersion = findVersionForTag(cached2.versions, tag);
2154
2259
  if (matchingVersion) {
2155
2260
  assertSameVersionCompatibility(matchingVersion);
2156
- if (!matchingVersion.host && !!remoteEntry?.host) {
2157
- matchingVersion.host = true;
2158
- matchingVersion.remotes.unshift(remote);
2159
- } else matchingVersion.remotes.push(remote);
2261
+ addRemoteToVersion(matchingVersion, remote, !matchingVersion.host && !!remoteEntry?.host);
2160
2262
  } else {
2161
- if (scopeType !== "strict") cached2.dirty = true;
2162
2263
  cached2.versions.push({
2163
2264
  tag,
2164
2265
  action: scopeType === "strict" ? "share" : "skip",
@@ -2173,13 +2274,23 @@ function createProcessRemoteEntries(config, ports) {
2173
2274
  // src/lib/core/2.app/steps/determine-shared-externals.ts
2174
2275
  function createDetermineSharedExternals(config, ports) {
2175
2276
  return () => {
2277
+ const memo = /* @__PURE__ */ new Map();
2278
+ const isCompatible = (tag, requiredVersion) => {
2279
+ const key = `${tag}|${requiredVersion}`;
2280
+ let hit = memo.get(key);
2281
+ if (hit === void 0) {
2282
+ hit = ports.versionCheck.isCompatible(tag, requiredVersion);
2283
+ memo.set(key, hit);
2284
+ }
2285
+ return hit;
2286
+ };
2176
2287
  for (const shareScope of ports.sharedExternalsRepo.getScopes()) {
2177
2288
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
2178
2289
  try {
2179
2290
  Object.entries(sharedExternals).filter(([_, e]) => e.dirty).forEach(
2180
2291
  ([name, external]) => ports.sharedExternalsRepo.addOrUpdate(
2181
2292
  name,
2182
- setVersionActions(name, external),
2293
+ setVersionActions(name, external, isCompatible),
2183
2294
  shareScope
2184
2295
  )
2185
2296
  );
@@ -2202,20 +2313,26 @@ function createDetermineSharedExternals(config, ports) {
2202
2313
  }
2203
2314
  return Promise.resolve();
2204
2315
  };
2205
- function uncoveredTears(external, winner) {
2316
+ function uncoveredTears(external, winner, accepts) {
2206
2317
  const basis = winner.remotes[0].entries;
2207
2318
  return external.versions.reduce((sum, v) => {
2208
2319
  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);
2320
+ if (!accepts(v, winner.tag)) return sum;
2321
+ return sum + v.remotes.reduce((n, r) => n + countUncoveredEntrypoints(r, basis), 0);
2211
2322
  }, 0);
2212
2323
  }
2213
- function setVersionActions(externalName, external) {
2324
+ function setVersionActions(externalName, external, isCompatible) {
2214
2325
  if (external.versions.length === 1) {
2215
2326
  external.versions[0].action = "share";
2327
+ applyEntrypointCoveragePolicy(externalName, external);
2216
2328
  external.dirty = false;
2217
2329
  return external;
2218
2330
  }
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));
2219
2336
  let sharedVersion = external.versions.find((v) => v.host);
2220
2337
  if (!sharedVersion && config.profile.latestSharedExternal) {
2221
2338
  sharedVersion = external.versions[0];
@@ -2225,11 +2342,17 @@ function createDetermineSharedExternals(config, ports) {
2225
2342
  let leastTears = Number.MAX_VALUE;
2226
2343
  external.versions.forEach((vA) => {
2227
2344
  const extraDownloads = external.versions.filter(
2228
- (vB) => !vB.remotes[0].cached && vB.remotes[0].strictVersion && !ports.versionCheck.isCompatible(vA.tag, vB.remotes[0].requiredVersion)
2345
+ (vB) => demands.get(vB).some((d) => !d.cached && d.strictVersion && !isCompatible(vA.tag, d.requiredVersion))
2229
2346
  ).length;
2230
- const tears = uncoveredTears(external, vA);
2231
- if (extraDownloads < leastExtraDownloads || extraDownloads === leastExtraDownloads && tears < leastTears) {
2347
+ if (extraDownloads < leastExtraDownloads) {
2232
2348
  leastExtraDownloads = extraDownloads;
2349
+ leastTears = uncoveredTears(external, vA, accepts);
2350
+ sharedVersion = vA;
2351
+ return;
2352
+ }
2353
+ if (extraDownloads > leastExtraDownloads) return;
2354
+ const tears = uncoveredTears(external, vA, accepts);
2355
+ if (tears < leastTears) {
2233
2356
  leastTears = tears;
2234
2357
  sharedVersion = vA;
2235
2358
  }
@@ -2239,39 +2362,81 @@ function createDetermineSharedExternals(config, ports) {
2239
2362
  throw new NFError(`[${externalName}] Could not determine shared version!`);
2240
2363
  }
2241
2364
  external.versions.forEach((v) => {
2242
- if (ports.versionCheck.isCompatible(sharedVersion.tag, v.remotes[0].requiredVersion)) {
2365
+ if (accepts(v, sharedVersion.tag)) {
2243
2366
  v.action = "skip";
2244
2367
  return;
2245
2368
  }
2246
- if (config.strict.strictExternalCompatibility && v.remotes[0].strictVersion) {
2369
+ const strict = objector(v, sharedVersion.tag);
2370
+ if (config.strict.strictExternalCompatibility && strict) {
2247
2371
  config.log.error(
2248
2372
  3,
2249
- `[${v.remotes[0].name}][${externalName}@${v.tag}] Is not compatible with requiredRange '${sharedVersion.remotes[0]?.requiredVersion}' from remote '${sharedVersion.remotes[0].name}'.`
2373
+ `[${strict.name}][${externalName}@${v.tag}] Is not compatible with requiredRange '${strict.requiredVersion}' of shared ${externalName}@${sharedVersion.tag}.`
2250
2374
  );
2251
2375
  throw new NFError(`External ${externalName}@${v.tag} could not be shared.`);
2252
2376
  }
2253
- v.action = v.remotes[0].strictVersion ? "scope" : "skip";
2377
+ v.action = strict ? "scope" : "skip";
2254
2378
  });
2255
2379
  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
- );
2269
- }
2270
- });
2271
- }
2380
+ applyEntrypointCoveragePolicy(externalName, external);
2272
2381
  external.dirty = false;
2273
2382
  return external;
2274
2383
  }
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
+ );
2398
+ }
2399
+ scopeTornRemotes(externalName, external, tears);
2400
+ }
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
+ });
2413
+ }
2414
+ return tears;
2415
+ }
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
+ );
2427
+ }
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));
2431
+ }
2432
+ }
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 });
2438
+ }
2439
+ }
2275
2440
  }
2276
2441
 
2277
2442
  // src/lib/core/2.app/steps/pooling/pool-graph.ts
@@ -2566,6 +2731,7 @@ function createGenerateImportMap(config, ports) {
2566
2731
  for (const [externalName, external] of Object.entries(sharedExternals)) {
2567
2732
  let override = void 0;
2568
2733
  let overrideScope = void 0;
2734
+ const cachedBefore = cachedFingerprint(external);
2569
2735
  for (const version of external.versions) {
2570
2736
  if (version.action === "scope") {
2571
2737
  for (const remote of version.remotes) {
@@ -2611,7 +2777,7 @@ function createGenerateImportMap(config, ports) {
2611
2777
  }
2612
2778
  for (const [packageName, file] of Object.entries(r.entries)) {
2613
2779
  if (packageName in source.entries) continue;
2614
- if (config.strict.strictEntryPointCoverage) {
2780
+ if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
2615
2781
  warnUncoveredEntrypoint(shareScope, externalName, r.name, packageName);
2616
2782
  continue;
2617
2783
  }
@@ -2625,9 +2791,14 @@ function createGenerateImportMap(config, ports) {
2625
2791
  addIntegrity(importMap, m.url, m.name, m.file);
2626
2792
  }
2627
2793
  }
2628
- ports.sharedExternalsRepo.addOrUpdate(externalName, external, shareScope);
2794
+ if (cachedFingerprint(external) !== cachedBefore) {
2795
+ ports.sharedExternalsRepo.addOrUpdate(externalName, external, shareScope);
2796
+ }
2629
2797
  }
2630
2798
  }
2799
+ function cachedFingerprint(external) {
2800
+ return external.versions.map((v) => v.remotes.map((r) => r.cached ? 1 : 0).join("")).join("|");
2801
+ }
2631
2802
  function findOverride(external, shareScope, externalName) {
2632
2803
  const sharedVersions = external.versions.filter((v) => v.action === "share");
2633
2804
  const scopedExternalName = `${shareScope}.${externalName}`;
@@ -2659,6 +2830,7 @@ function createGenerateImportMap(config, ports) {
2659
2830
  function addGlobalSharedExternals(importMap, chunkBundles) {
2660
2831
  const sharedExternals = ports.sharedExternalsRepo.getFromScope();
2661
2832
  for (const [externalName, external] of Object.entries(sharedExternals)) {
2833
+ const cachedBefore = cachedFingerprint(external);
2662
2834
  for (const version of external.versions) {
2663
2835
  if (version.action === "skip") continue;
2664
2836
  if (version.action === "scope") {
@@ -2686,31 +2858,39 @@ function createGenerateImportMap(config, ports) {
2686
2858
  }
2687
2859
  registerBundleChunks(chunkBundles, version.remotes[0].name, version.remotes[0].bundle);
2688
2860
  version.remotes[0].cached = true;
2861
+ selfFillUncovered(importMap, chunkBundles, externalName, version.remotes, 1);
2689
2862
  }
2690
2863
  for (const version of external.versions) {
2691
2864
  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
- }
2865
+ selfFillUncovered(importMap, chunkBundles, externalName, version.remotes);
2866
+ }
2867
+ if (cachedFingerprint(external) !== cachedBefore) {
2868
+ ports.sharedExternalsRepo.addOrUpdate(externalName, external);
2706
2869
  }
2707
- ports.sharedExternalsRepo.addOrUpdate(externalName, external);
2708
2870
  }
2709
2871
  return importMap;
2710
2872
  }
2873
+ function selfFillUncovered(importMap, chunkBundles, externalName, remotes, from = 0) {
2874
+ for (let i = from; i < remotes.length; i++) {
2875
+ const remote = remotes[i];
2876
+ for (const [packageName, file] of Object.entries(remote.entries)) {
2877
+ if (importMap.imports[packageName]) continue;
2878
+ if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
2879
+ warnUncoveredEntrypoint(GLOBAL_SCOPE, externalName, remote.name, packageName);
2880
+ continue;
2881
+ }
2882
+ const scope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
2883
+ const url = join(scope, file);
2884
+ addToGlobal(importMap, { [packageName]: url });
2885
+ addIntegrity(importMap, url, remote.name, file);
2886
+ registerBundleChunks(chunkBundles, remote.name, remote.bundle);
2887
+ remote.cached = true;
2888
+ }
2889
+ }
2890
+ }
2711
2891
  function warnUncoveredEntrypoint(scope, externalName, remoteName, packageName) {
2712
2892
  const msg = `[${scope}][${externalName}][${remoteName}] Entrypoint '${packageName}' is not covered by the shared version.`;
2713
- if (config.strict.strictImportMap) {
2893
+ if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
2714
2894
  config.log.error(4, msg);
2715
2895
  throw new NFError("Could not create ImportMap.");
2716
2896
  }
@@ -2899,18 +3079,23 @@ function createGetRemoteEntry(config, ports) {
2899
3079
  // src/lib/core/2.app/steps/update-cache.ts
2900
3080
  function createUpdateCache(config, ports) {
2901
3081
  const storeRemoteEntry = createStoreRemoteEntry(config, ports, 8);
3082
+ const removeCachedRemoteEntries = createRemoveCachedRemoteEntries(ports);
2902
3083
  return (remoteEntry) => {
2903
3084
  try {
2904
3085
  const actions = {};
3086
+ if (remoteEntry?.override) removeCachedRemoteEntries(/* @__PURE__ */ new Set([remoteEntry.name]));
2905
3087
  storeRemoteEntry(remoteEntry, (entry, external, ctx) => {
2906
3088
  const { action, sharedVersion } = resolveSharedExternal(entry, external, ctx);
2907
3089
  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
- );
3090
+ if (action === "skip" && sharedVersion?.remotes[0]?.entries) {
3091
+ actions[external.packageName].covered = Object.keys(sharedVersion.remotes[0].entries);
3092
+ if (external.shareScope) {
3093
+ actions[external.packageName].override = resolveOverrideEntries(
3094
+ entry,
3095
+ external,
3096
+ sharedVersion
3097
+ );
3098
+ }
2914
3099
  }
2915
3100
  });
2916
3101
  return Promise.resolve({ entry: remoteEntry, actions });
@@ -2938,13 +3123,29 @@ function createUpdateCache(config, ports) {
2938
3123
  }
2939
3124
  config.log.warn(8, errorMsg);
2940
3125
  }
2941
- if (action === "skip" && config.strict.strictEntryPointCoverage && sharedVersion && Object.keys(remote.entries).some((e) => !(e in sharedVersion.remotes[0].entries))) {
2942
- action = "scope";
3126
+ if (action === "skip" && sharedVersion) {
3127
+ const uncovered = uncoveredEntrypoints(remote, sharedVersion.remotes[0].entries);
3128
+ if (uncovered.length > 0) {
3129
+ const msg = `[${sharedInfo.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}][${sharedInfo.packageName}] Entrypoints not covered by the shared version: ${uncovered.join(", ")}.`;
3130
+ if (config.strict.strictEntryPointCoverage) {
3131
+ config.log.error(8, msg);
3132
+ throw new NFError(`Could not process remote '${remoteEntry.name}'`);
3133
+ }
3134
+ if (config.profile.scopeUncoveredEntrypoints) {
3135
+ config.log.debug(8, msg);
3136
+ action = "scope";
3137
+ }
3138
+ }
2943
3139
  }
2944
- const matchingVersion = cached2.versions.find((cached3) => cached3.tag === tag);
2945
- if (matchingVersion) {
3140
+ const matchingVersion = findVersionForTag(cached2.versions, tag);
3141
+ if (action === "scope") {
3142
+ remote.cached = true;
3143
+ const scoped = cached2.versions.find((v) => v.tag === tag && v.action === "scope");
3144
+ if (scoped) scoped.remotes.push(remote);
3145
+ else cached2.versions.push({ tag, action, host: false, remotes: [remote] });
3146
+ } else if (matchingVersion) {
2946
3147
  assertSameVersionCompatibility(matchingVersion);
2947
- matchingVersion.remotes.push(remote);
3148
+ addRemoteToVersion(matchingVersion, remote);
2948
3149
  } else {
2949
3150
  if (!sharedVersion) action = "share";
2950
3151
  remote.cached = action !== "skip";
@@ -3047,22 +3248,24 @@ function createConvertToImportMap(config, ports) {
3047
3248
  return;
3048
3249
  }
3049
3250
  if (actions[external.packageName].action === "skip") {
3050
- const override = actions[external.packageName].override;
3051
- if (!external.shareScope) return;
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
+ }
3052
3258
  if (override) {
3053
3259
  Object.entries(override).forEach(([packageName, url]) => {
3054
3260
  addToScopes(remoteEntryScope, packageName, url, importMap);
3055
3261
  });
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
- });
3262
+ serveUncovered(
3263
+ remoteEntry,
3264
+ external,
3265
+ covered ?? Object.keys(override),
3266
+ remoteEntryScope,
3267
+ importMap
3268
+ );
3066
3269
  return;
3067
3270
  }
3068
3271
  log.error(
@@ -3129,9 +3332,22 @@ function createConvertToImportMap(config, ports) {
3129
3332
  });
3130
3333
  return importMap;
3131
3334
  }
3335
+ function serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap) {
3336
+ const provided = new Set(covered);
3337
+ Object.entries(external.entries).forEach(([packageName, fileName]) => {
3338
+ if (provided.has(packageName)) return;
3339
+ if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
3340
+ warnUncoveredEntrypoint(remoteEntry.name, external.packageName, packageName);
3341
+ return;
3342
+ }
3343
+ const url = join(remoteEntryScope, fileName);
3344
+ addToScopes(remoteEntryScope, packageName, url, importMap);
3345
+ addIntegrity(importMap, url, remoteEntry.integrity, fileName);
3346
+ });
3347
+ }
3132
3348
  function warnUncoveredEntrypoint(remoteName, externalName, packageName) {
3133
3349
  const msg = `[${remoteName}][${externalName}] Entrypoint '${packageName}' is not covered by the override.`;
3134
- if (config.strict.strictImportMap) {
3350
+ if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
3135
3351
  log.error(9, msg);
3136
3352
  throw new NFError("Could not create ImportMap.");
3137
3353
  }