@softarc/native-federation-orchestrator 4.6.1 → 4.8.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 (81) hide show
  1. package/README.md +2 -2
  2. package/fesm2022/@softarc/native-federation-orchestrator.mjs +1880 -1281
  3. package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
  4. package/fesm2022/audit.mjs +1 -1
  5. package/fesm2022/audit.mjs.map +2 -2
  6. package/fesm2022/module-federation.mjs +199 -32
  7. package/fesm2022/module-federation.mjs.map +3 -3
  8. package/fesm2022/node.mjs +1867 -1257
  9. package/fesm2022/node.mjs.map +4 -4
  10. package/fesm2022/options.mjs +7 -3
  11. package/fesm2022/options.mjs.map +2 -2
  12. package/fesm2022/registry.mjs +1 -1
  13. package/fesm2022/registry.mjs.map +2 -2
  14. package/fesm2022/sdk.mjs +1 -1
  15. package/fesm2022/sdk.mjs.map +2 -2
  16. package/init-registry.mjs +1 -1
  17. package/node-loader/loader.mjs +7 -4
  18. package/node-loader/loader.mjs.map +2 -2
  19. package/package.json +9 -1
  20. package/quickstart.mjs +14 -15
  21. package/types/lib/core/1.domain/externals/basis.d.ts +2 -15
  22. package/types/lib/core/1.domain/externals/claims.d.ts +22 -0
  23. package/types/lib/core/1.domain/externals/compatibility.d.ts +9 -0
  24. package/types/lib/core/1.domain/externals/external.contract.d.ts +4 -0
  25. package/types/lib/core/1.domain/externals/rows.d.ts +9 -0
  26. package/types/lib/core/1.domain/externals/serving.d.ts +52 -0
  27. package/types/lib/core/1.domain/externals/specifier.d.ts +13 -0
  28. package/types/lib/core/1.domain/externals/version.contract.d.ts +2 -0
  29. package/types/lib/core/1.domain/pooling/builds.d.ts +20 -0
  30. package/types/lib/core/1.domain/pooling/election.d.ts +40 -0
  31. package/types/lib/core/1.domain/pooling/gate.d.ts +62 -0
  32. package/types/lib/core/1.domain/pooling/membership.d.ts +17 -0
  33. package/types/lib/core/1.domain/pooling/plan.d.ts +9 -0
  34. package/types/lib/core/1.domain/pooling/pool-state.d.ts +4 -0
  35. package/types/lib/core/1.domain/pooling/records.d.ts +16 -0
  36. package/types/lib/core/1.domain/pooling/rules.d.ts +6 -0
  37. package/types/lib/core/1.domain/remote-entry/load-actions.contract.d.ts +4 -0
  38. package/types/lib/core/1.domain/remote-entry/remote-entry.contract.d.ts +0 -2
  39. package/types/lib/core/2.app/config/index.d.ts +2 -1
  40. package/types/lib/core/2.app/config/mode.contract.d.ts +8 -5
  41. package/types/lib/core/2.app/config/orchestrator-global.contract.d.ts +15 -0
  42. package/types/lib/core/2.app/config/storage.contract.d.ts +19 -2
  43. package/types/lib/core/2.app/driver-ports/init/drivers.contract.d.ts +4 -2
  44. package/types/lib/core/2.app/driver-ports/init/for-committing-changes.port.d.ts +1 -0
  45. package/types/lib/core/2.app/driver-ports/init/for-deciding-serving-builds.port.d.ts +1 -0
  46. package/types/lib/core/2.app/driver-ports/init/for-determining-shared-externals.port.d.ts +1 -8
  47. package/types/lib/core/2.app/driver-ports/init/for-pooling-dynamic-externals.port.d.ts +2 -1
  48. package/types/lib/core/2.app/driver-ports/init/for-pooling-shared-externals.port.d.ts +1 -3
  49. package/types/lib/core/2.app/driver-ports/init/for-rolling-back-changes.port.d.ts +1 -0
  50. package/types/lib/core/2.app/driver-ports/init/for-updating-cache.d.ts +3 -2
  51. package/types/lib/core/2.app/driver-ports/init/index.d.ts +2 -0
  52. package/types/lib/core/2.app/driving-ports/for-remote-info-storage.port.d.ts +1 -0
  53. package/types/lib/core/2.app/driving-ports/for-scoped-externals-storage.port.d.ts +1 -0
  54. package/types/lib/core/2.app/driving-ports/for-shared-chunks-storage.port.d.ts +1 -0
  55. package/types/lib/core/2.app/driving-ports/for-shared-externals-storage.port.d.ts +5 -1
  56. package/types/lib/core/2.app/steps/apply-winner.d.ts +6 -13
  57. package/types/lib/core/2.app/steps/decide-serving-builds.d.ts +5 -0
  58. package/types/lib/core/2.app/steps/pooling/pool-shared-externals.d.ts +0 -29
  59. package/types/lib/core/2.app/steps/rollback-changes.d.ts +5 -0
  60. package/types/lib/core/2.app/steps/store-remote-entry.d.ts +2 -2
  61. package/types/lib/core/3.adapters/storage/cache-state.d.ts +5 -0
  62. package/types/lib/core/3.adapters/storage/chunk.repository.d.ts +1 -1
  63. package/types/lib/core/3.adapters/storage/remote-info.repository.d.ts +1 -1
  64. package/types/lib/core/3.adapters/storage/scoped-externals.repository.d.ts +1 -1
  65. package/types/lib/core/3.adapters/storage/shared-externals.repository.d.ts +1 -1
  66. package/types/lib/core/4.config/orchestrator-version.d.ts +1 -0
  67. package/types/lib/core/4.config/storage/publish-storage-info.d.ts +9 -0
  68. package/types/lib/core/4.config/storage/storage.config.d.ts +1 -1
  69. package/types/lib/core/5.di/federation-result.factory.d.ts +1 -1
  70. package/types/lib/sdk.index.d.ts +4 -0
  71. package/types/lib/utils/compare-strings.d.ts +2 -0
  72. package/types/lib/utils/deep-readonly.d.ts +3 -0
  73. package/types/lib/core/2.app/driver-ports/init/for-marking-pools-for-reelection.port.d.ts +0 -6
  74. package/types/lib/core/2.app/steps/pooling/anchoring.d.ts +0 -69
  75. package/types/lib/core/2.app/steps/pooling/mark-pools-for-reelection.d.ts +0 -5
  76. package/types/lib/core/2.app/steps/pooling/pool-graph.d.ts +0 -30
  77. package/types/lib/core/2.app/steps/pooling/pool-views.d.ts +0 -63
  78. package/types/lib/core/2.app/steps/pooling/pool.types.d.ts +0 -35
  79. package/types/lib/core/2.app/steps/pooling/pool.util.d.ts +0 -18
  80. package/types/lib/testing/handlers/log.handler.d.ts +0 -3
  81. package/types/lib/testing/pooling/no-tear.d.ts +0 -37
package/fesm2022/node.mjs CHANGED
@@ -223,7 +223,7 @@ var require_semver = __commonJS({
223
223
  }
224
224
  return true;
225
225
  };
226
- var SemVer = class _SemVer {
226
+ var SemVer2 = class _SemVer {
227
227
  constructor(version, options) {
228
228
  options = parseOptions(options);
229
229
  if (version instanceof _SemVer) {
@@ -490,7 +490,7 @@ var require_semver = __commonJS({
490
490
  return this;
491
491
  }
492
492
  };
493
- module.exports = SemVer;
493
+ module.exports = SemVer2;
494
494
  }
495
495
  });
496
496
 
@@ -498,13 +498,13 @@ var require_semver = __commonJS({
498
498
  var require_parse = __commonJS({
499
499
  "node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/parse.js"(exports, module) {
500
500
  "use strict";
501
- var SemVer = require_semver();
501
+ var SemVer2 = require_semver();
502
502
  var parse = (version, options, throwErrors = false) => {
503
- if (version instanceof SemVer) {
503
+ if (version instanceof SemVer2) {
504
504
  return version;
505
505
  }
506
506
  try {
507
- return new SemVer(version, options);
507
+ return new SemVer2(version, options);
508
508
  } catch (er) {
509
509
  if (!throwErrors) {
510
510
  return null;
@@ -571,8 +571,8 @@ var require_lrucache = __commonJS({
571
571
  var require_compare = __commonJS({
572
572
  "node_modules/.pnpm/semver@7.8.5/node_modules/semver/functions/compare.js"(exports, module) {
573
573
  "use strict";
574
- var SemVer = require_semver();
575
- var compare = (a, b, loose) => new SemVer(a, loose).compare(new SemVer(b, loose));
574
+ var SemVer2 = require_semver();
575
+ var compare = (a, b, loose) => new SemVer2(a, loose).compare(new SemVer2(b, loose));
576
576
  module.exports = compare;
577
577
  }
578
578
  });
@@ -730,7 +730,7 @@ var require_comparator = __commonJS({
730
730
  if (!m[2]) {
731
731
  this.semver = ANY;
732
732
  } else {
733
- this.semver = new SemVer(m[2], this.options.loose);
733
+ this.semver = new SemVer2(m[2], this.options.loose);
734
734
  }
735
735
  }
736
736
  toString() {
@@ -743,7 +743,7 @@ var require_comparator = __commonJS({
743
743
  }
744
744
  if (typeof version === "string") {
745
745
  try {
746
- version = new SemVer(version, this.options);
746
+ version = new SemVer2(version, this.options);
747
747
  } catch (er) {
748
748
  return false;
749
749
  }
@@ -795,7 +795,7 @@ var require_comparator = __commonJS({
795
795
  var { safeRe: re, t } = require_re();
796
796
  var cmp = require_cmp();
797
797
  var debug = require_debug();
798
- var SemVer = require_semver();
798
+ var SemVer2 = require_semver();
799
799
  var Range = require_range();
800
800
  }
801
801
  });
@@ -931,7 +931,7 @@ var require_range = __commonJS({
931
931
  }
932
932
  if (typeof version === "string") {
933
933
  try {
934
- version = new SemVer(version, this.options);
934
+ version = new SemVer2(version, this.options);
935
935
  } catch (er) {
936
936
  return false;
937
937
  }
@@ -950,7 +950,7 @@ var require_range = __commonJS({
950
950
  var parseOptions = require_parse_options();
951
951
  var Comparator = require_comparator();
952
952
  var debug = require_debug();
953
- var SemVer = require_semver();
953
+ var SemVer2 = require_semver();
954
954
  var {
955
955
  safeRe: re,
956
956
  src,
@@ -1206,16 +1206,16 @@ var require_satisfies = __commonJS({
1206
1206
  var require_min_version = __commonJS({
1207
1207
  "node_modules/.pnpm/semver@7.8.5/node_modules/semver/ranges/min-version.js"(exports, module) {
1208
1208
  "use strict";
1209
- var SemVer = require_semver();
1209
+ var SemVer2 = require_semver();
1210
1210
  var Range = require_range();
1211
1211
  var gt = require_gt();
1212
1212
  var minVersion = (range, loose) => {
1213
1213
  range = new Range(range, loose);
1214
- let minver = new SemVer("0.0.0");
1214
+ let minver = new SemVer2("0.0.0");
1215
1215
  if (range.test(minver)) {
1216
1216
  return minver;
1217
1217
  }
1218
- minver = new SemVer("0.0.0-0");
1218
+ minver = new SemVer2("0.0.0-0");
1219
1219
  if (range.test(minver)) {
1220
1220
  return minver;
1221
1221
  }
@@ -1224,7 +1224,7 @@ var require_min_version = __commonJS({
1224
1224
  const comparators = range.set[i];
1225
1225
  let setMin = null;
1226
1226
  comparators.forEach((comparator) => {
1227
- const compver = new SemVer(comparator.semver.version);
1227
+ const compver = new SemVer2(comparator.semver.version);
1228
1228
  switch (comparator.operator) {
1229
1229
  case ">":
1230
1230
  if (compver.prerelease.length === 0) {
@@ -1288,19 +1288,30 @@ var createBrowser = (config) => {
1288
1288
  // src/lib/core/3.adapters/checks/version.check.ts
1289
1289
  var import_valid = __toESM(require_valid());
1290
1290
  var import_satisfies = __toESM(require_satisfies());
1291
- var import_compare = __toESM(require_compare());
1291
+ var import_semver = __toESM(require_semver());
1292
1292
  var import_min_version = __toESM(require_min_version());
1293
1293
  var import_valid2 = __toESM(require_valid2());
1294
1294
  var createVersionCheck = () => {
1295
+ const parsed = /* @__PURE__ */ new Map();
1296
+ const parse = (v) => {
1297
+ let hit = parsed.get(v);
1298
+ if (!hit) parsed.set(v, hit = new import_semver.default(v, true));
1299
+ return hit;
1300
+ };
1301
+ const satisfied = /* @__PURE__ */ new Map();
1295
1302
  return {
1296
1303
  isValidSemver: function(version) {
1297
1304
  return (0, import_valid.default)(version) !== null;
1298
1305
  },
1299
1306
  isCompatible: function(version, range) {
1300
- return (0, import_satisfies.default)(version, range);
1307
+ let byVersion = satisfied.get(range);
1308
+ if (!byVersion) satisfied.set(range, byVersion = /* @__PURE__ */ new Map());
1309
+ let hit = byVersion.get(version);
1310
+ if (hit === void 0) byVersion.set(version, hit = (0, import_satisfies.default)(version, range));
1311
+ return hit;
1301
1312
  },
1302
1313
  compare: function(versionA, versionB) {
1303
- return (0, import_compare.default)(versionA, versionB, true);
1314
+ return parse(versionA).compare(parse(versionB));
1304
1315
  },
1305
1316
  smallestVersion: function(versionRange) {
1306
1317
  if (!(0, import_valid2.default)(versionRange)) return "0.0.0";
@@ -1313,7 +1324,7 @@ var createVersionCheck = () => {
1313
1324
  // src/lib/core/native-federation.error.ts
1314
1325
  var NFError = class extends Error {
1315
1326
  constructor(message, cause) {
1316
- super(message, cause);
1327
+ super(message, cause ? { cause } : void 0);
1317
1328
  this.name = "NFError";
1318
1329
  }
1319
1330
  };
@@ -1430,6 +1441,15 @@ var createNoopSSE = () => ({
1430
1441
  }
1431
1442
  });
1432
1443
 
1444
+ // src/lib/core/2.app/config/storage.contract.ts
1445
+ var STORAGE_KEYS = {
1446
+ remotes: "remotes",
1447
+ sharedExternals: "shared-externals",
1448
+ scopedExternals: "scoped-externals",
1449
+ sharedChunks: "shared-chunks",
1450
+ cacheState: "cache-state"
1451
+ };
1452
+
1433
1453
  // src/lib/utils/path.ts
1434
1454
  function join(pathA, pathB) {
1435
1455
  pathA = pathA.endsWith("/") ? pathA.slice(0, -1) : pathA;
@@ -1487,9 +1507,9 @@ var Optional = class _Optional {
1487
1507
 
1488
1508
  // src/lib/core/3.adapters/storage/remote-info.repository.ts
1489
1509
  var createRemoteInfoRepository = (config) => {
1490
- const STORAGE = config.storage("remotes", {});
1510
+ const STORAGE = config.storage(STORAGE_KEYS.remotes, {});
1491
1511
  if (config.clearStorage) STORAGE.clear();
1492
- const _cache = STORAGE.get() ?? {};
1512
+ let _cache = STORAGE.get() ?? {};
1493
1513
  let _dirty = false;
1494
1514
  return {
1495
1515
  contains: function(remoteName) {
@@ -1523,15 +1543,20 @@ var createRemoteInfoRepository = (config) => {
1523
1543
  STORAGE.set(_cache);
1524
1544
  _dirty = false;
1525
1545
  return this;
1546
+ },
1547
+ rollback: function() {
1548
+ _cache = STORAGE.get() ?? {};
1549
+ _dirty = false;
1550
+ return this;
1526
1551
  }
1527
1552
  };
1528
1553
  };
1529
1554
 
1530
1555
  // src/lib/core/3.adapters/storage/scoped-externals.repository.ts
1531
1556
  var createScopedExternalsRepository = (config) => {
1532
- const STORAGE = config.storage("scoped-externals", {});
1557
+ const STORAGE = config.storage(STORAGE_KEYS.scopedExternals, {});
1533
1558
  if (config.clearStorage) STORAGE.clear();
1534
- const _cache = STORAGE.get() ?? {};
1559
+ let _cache = STORAGE.get() ?? {};
1535
1560
  let _dirty = false;
1536
1561
  return {
1537
1562
  addExternal: function(remoteName, external, version) {
@@ -1558,6 +1583,11 @@ var createScopedExternalsRepository = (config) => {
1558
1583
  STORAGE.set(_cache);
1559
1584
  _dirty = false;
1560
1585
  return this;
1586
+ },
1587
+ rollback: function() {
1588
+ _cache = STORAGE.get() ?? {};
1589
+ _dirty = false;
1590
+ return this;
1561
1591
  }
1562
1592
  };
1563
1593
  };
@@ -1566,34 +1596,48 @@ var createScopedExternalsRepository = (config) => {
1566
1596
  var GLOBAL_SCOPE = "__GLOBAL__";
1567
1597
  var STRICT_SCOPE = "strict";
1568
1598
 
1599
+ // src/lib/core/3.adapters/storage/cache-state.ts
1600
+ var createCacheState = (storage) => {
1601
+ const entry = storage(STORAGE_KEYS.cacheState, null);
1602
+ const read = () => {
1603
+ const stored = entry.get();
1604
+ if (typeof stored !== "object" || stored === null) return {};
1605
+ const { version, undecidedScopes } = stored;
1606
+ return {
1607
+ ...typeof version === "string" ? { version } : {},
1608
+ ...Array.isArray(undecidedScopes) ? { undecidedScopes: undecidedScopes.filter((s) => typeof s === "string") } : {}
1609
+ };
1610
+ };
1611
+ return {
1612
+ read,
1613
+ update: (fields) => void entry.set({ ...read(), ...fields })
1614
+ };
1615
+ };
1616
+
1569
1617
  // src/lib/core/3.adapters/storage/shared-externals.repository.ts
1570
1618
  var createSharedExternalsRepository = (config) => {
1571
1619
  const STORAGE = config.storage(
1572
- "shared-externals",
1620
+ STORAGE_KEYS.sharedExternals,
1573
1621
  { [GLOBAL_SCOPE]: {} }
1574
1622
  );
1575
- if (config.clearStorage) STORAGE.clear();
1576
- const _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
1577
- let _dirty = false;
1623
+ const cacheState = createCacheState(config.storage);
1624
+ const storedUndecided = () => new Set(cacheState.read().undecidedScopes);
1625
+ if (config.clearStorage) {
1626
+ STORAGE.clear();
1627
+ cacheState.update({ undecidedScopes: [] });
1628
+ }
1629
+ let _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
1630
+ const _uncommitted = /* @__PURE__ */ new Set();
1631
+ let _undecided = storedUndecided();
1632
+ let _undecidedChanged = false;
1578
1633
  return {
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;
1589
- },
1590
1634
  getFromScope: function(shareScope) {
1591
1635
  return { ..._cache[shareScope ?? GLOBAL_SCOPE] };
1592
1636
  },
1593
1637
  addOrUpdate: function(externalName, external, shareScope) {
1594
1638
  if (!_cache[shareScope ?? GLOBAL_SCOPE]) _cache[shareScope ?? GLOBAL_SCOPE] = {};
1595
1639
  _cache[shareScope ?? GLOBAL_SCOPE][externalName] = external;
1596
- _dirty = true;
1640
+ _uncommitted.add(shareScope ?? GLOBAL_SCOPE);
1597
1641
  return this;
1598
1642
  },
1599
1643
  getScopes: function(o = { includeGlobal: true }) {
@@ -1603,7 +1647,7 @@ var createSharedExternalsRepository = (config) => {
1603
1647
  // Batched: one traversal of the graph per init instead of one per overridden remote.
1604
1648
  removeFromAllScopes: function(remoteNames) {
1605
1649
  if (remoteNames.size === 0) return;
1606
- Object.values(_cache).forEach((scope) => {
1650
+ Object.entries(_cache).forEach(([scopeName, scope]) => {
1607
1651
  const removeExternals = [];
1608
1652
  Object.entries(scope).forEach(([name, external]) => {
1609
1653
  let removedCopy = false;
@@ -1619,12 +1663,12 @@ var createSharedExternalsRepository = (config) => {
1619
1663
  if (keep !== remotes.length) {
1620
1664
  remotes.length = keep;
1621
1665
  removedCopy = true;
1622
- _dirty = true;
1666
+ _uncommitted.add(scopeName);
1623
1667
  if (host !== void 0 && remoteNames.has(host)) version.host = false;
1624
1668
  }
1625
1669
  if (remotes.length === 0) {
1626
1670
  external.versions.splice(i, 1);
1627
- _dirty = true;
1671
+ _uncommitted.add(scopeName);
1628
1672
  }
1629
1673
  }
1630
1674
  if (removedCopy) {
@@ -1632,7 +1676,17 @@ var createSharedExternalsRepository = (config) => {
1632
1676
  if (external.versions.length === 0) removeExternals.push(name);
1633
1677
  }
1634
1678
  });
1635
- removeExternals.forEach((name) => delete scope[name]);
1679
+ const lostPools = /* @__PURE__ */ new Set();
1680
+ for (const name of removeExternals) {
1681
+ const poolName = scope[name].poolName;
1682
+ if (poolName !== void 0) lostPools.add(poolName);
1683
+ delete scope[name];
1684
+ }
1685
+ if (lostPools.size > 0) {
1686
+ for (const external of Object.values(scope))
1687
+ if (external.poolName !== void 0 && lostPools.has(external.poolName))
1688
+ external.dirty = true;
1689
+ }
1636
1690
  });
1637
1691
  },
1638
1692
  scopeType: function(shareScope) {
@@ -1650,10 +1704,36 @@ var createSharedExternalsRepository = (config) => {
1650
1704
  tryGet: function(external, shareScope) {
1651
1705
  return Optional.of(_cache[shareScope ?? GLOBAL_SCOPE]?.[external]);
1652
1706
  },
1707
+ uncommittedScopes: function() {
1708
+ return [..._uncommitted];
1709
+ },
1710
+ markUndecided: function(shareScope) {
1711
+ if (_undecided.has(shareScope)) return;
1712
+ _undecided.add(shareScope);
1713
+ _undecidedChanged = true;
1714
+ },
1715
+ isUndecided: function(shareScope) {
1716
+ return _undecided.has(shareScope);
1717
+ },
1718
+ markDecided: function(shareScope) {
1719
+ if (_undecided.delete(shareScope)) _undecidedChanged = true;
1720
+ },
1721
+ // Records before marks: should their write throw, the stored marks still send the next init to decide them. A
1722
+ // failed cache-state write after the records loses only this load's marks.
1653
1723
  commit: function() {
1654
- if (!_dirty) return this;
1655
- STORAGE.set(_cache);
1656
- _dirty = false;
1724
+ if (_uncommitted.size > 0) {
1725
+ STORAGE.set(_cache);
1726
+ _uncommitted.clear();
1727
+ }
1728
+ if (_undecidedChanged) cacheState.update({ undecidedScopes: [..._undecided] });
1729
+ _undecidedChanged = false;
1730
+ return this;
1731
+ },
1732
+ rollback: function() {
1733
+ _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
1734
+ _uncommitted.clear();
1735
+ _undecided = storedUndecided();
1736
+ _undecidedChanged = false;
1657
1737
  return this;
1658
1738
  }
1659
1739
  };
@@ -1661,9 +1741,9 @@ var createSharedExternalsRepository = (config) => {
1661
1741
 
1662
1742
  // src/lib/core/3.adapters/storage/chunk.repository.ts
1663
1743
  var createChunkRepository = (config) => {
1664
- const STORAGE = config.storage("shared-chunks", {});
1744
+ const STORAGE = config.storage(STORAGE_KEYS.sharedChunks, {});
1665
1745
  if (config.clearStorage) STORAGE.clear();
1666
- const _cache = STORAGE.get() ?? {};
1746
+ let _cache = STORAGE.get() ?? {};
1667
1747
  let _dirty = false;
1668
1748
  return {
1669
1749
  addOrReplace: function(remoteName, bundleName, chunks) {
@@ -1688,6 +1768,11 @@ var createChunkRepository = (config) => {
1688
1768
  STORAGE.set(_cache);
1689
1769
  _dirty = false;
1690
1770
  return this;
1771
+ },
1772
+ rollback: function() {
1773
+ _cache = STORAGE.get() ?? {};
1774
+ _dirty = false;
1775
+ return this;
1691
1776
  }
1692
1777
  };
1693
1778
  };
@@ -1891,18 +1976,74 @@ var globalThisStorageEntry = (namespace) => (key, initialValue) => {
1891
1976
  };
1892
1977
  return entry;
1893
1978
  };
1979
+ globalThisStorageEntry.type = "globalThis";
1980
+ Object.freeze(globalThisStorageEntry);
1981
+
1982
+ // src/lib/core/4.config/orchestrator-version.ts
1983
+ var ORCHESTRATOR_VERSION = true ? "4.8.0" : "dev";
1984
+
1985
+ // src/lib/core/4.config/storage/publish-storage-info.ts
1986
+ var KEYS = Object.freeze(Object.values(STORAGE_KEYS));
1987
+ var isStorageKey = (key) => KEYS.includes(key);
1988
+ var createStorageInfo = ({
1989
+ type,
1990
+ namespace,
1991
+ storage,
1992
+ exposeGetter
1993
+ }) => {
1994
+ const info = {
1995
+ version: ORCHESTRATOR_VERSION,
1996
+ type,
1997
+ namespace,
1998
+ keys: KEYS
1999
+ };
2000
+ if (!exposeGetter) return Object.freeze(info);
2001
+ return Object.freeze({
2002
+ ...info,
2003
+ // Cloned again because custom storages may hand out live references.
2004
+ get: (key) => isStorageKey(key) ? cloneEntry(key, storage(key, void 0).get()) : void 0
2005
+ });
2006
+ };
2007
+ var publishStorageInfo = (options) => {
2008
+ try {
2009
+ globalThis.__NF_ORCHESTRATOR__ = Object.freeze({
2010
+ storage: Object.freeze({
2011
+ ...globalThis.__NF_ORCHESTRATOR__?.storage,
2012
+ [options.namespace]: createStorageInfo(options)
2013
+ })
2014
+ });
2015
+ } catch {
2016
+ }
2017
+ };
1894
2018
 
1895
2019
  // src/lib/core/4.config/storage/storage.config.ts
1896
- var createStorageConfig = (override) => ({
1897
- storage: override.storage ? override.storage(override.storageNamespace ?? "__NATIVE_FEDERATION__") : globalThisStorageEntry(override.storageNamespace ?? "__NATIVE_FEDERATION__"),
1898
- clearStorage: override.clearStorage ?? false
1899
- });
2020
+ var isOutdatedCache = (storage) => {
2021
+ const cacheState = createCacheState(storage);
2022
+ if (cacheState.read().version === ORCHESTRATOR_VERSION) return false;
2023
+ cacheState.update({ version: ORCHESTRATOR_VERSION, undecidedScopes: [] });
2024
+ return Object.values(STORAGE_KEYS).filter((key) => key !== STORAGE_KEYS.cacheState).some((key) => storage(key, null).get() !== null);
2025
+ };
2026
+ var createStorageConfig = (override) => {
2027
+ const creator = override.storage ?? globalThisStorageEntry;
2028
+ const namespace = override.storageNamespace ?? "__NATIVE_FEDERATION__";
2029
+ const storage = creator(namespace);
2030
+ publishStorageInfo({
2031
+ type: creator.type ?? "custom",
2032
+ namespace,
2033
+ storage,
2034
+ exposeGetter: override.exposeStorageGetter ?? true
2035
+ });
2036
+ const outdated = isOutdatedCache(storage);
2037
+ return {
2038
+ storage,
2039
+ clearStorage: (override.clearStorage ?? false) || outdated
2040
+ };
2041
+ };
1900
2042
 
1901
2043
  // src/lib/core/4.config/mode/default.profile.ts
1902
2044
  var defaultProfile = {
1903
2045
  latestSharedExternal: false,
1904
2046
  skipInvalidExternalVersions: false,
1905
- scopeUncoveredEntrypoints: false,
1906
2047
  overrideCachedRemotes: "init-only",
1907
2048
  overrideCachedRemotesIfURLMatches: false
1908
2049
  };
@@ -1914,22 +2055,19 @@ var createModeConfig = (override) => {
1914
2055
  strictExternalCompatibility: override.strict,
1915
2056
  strictExternalSameVersionCompatibility: override.strict,
1916
2057
  strictExternalVersion: override.strict,
1917
- strictImportMap: override.strict,
1918
- strictEntryPointCoverage: override.strict
2058
+ strictImportMap: override.strict
1919
2059
  } : {
1920
2060
  strictRemoteEntry: override.strict?.strictRemoteEntry ?? false,
1921
2061
  strictExternalCompatibility: override.strict?.strictExternalCompatibility ?? false,
1922
2062
  strictExternalSameVersionCompatibility: override.strict?.strictExternalSameVersionCompatibility ?? false,
1923
2063
  strictExternalVersion: override.strict?.strictExternalVersion ?? false,
1924
- strictImportMap: override.strict?.strictImportMap ?? false,
1925
- strictEntryPointCoverage: override.strict?.strictEntryPointCoverage ?? false
2064
+ strictImportMap: override.strict?.strictImportMap ?? false
1926
2065
  };
1927
2066
  return {
1928
2067
  strict: strictnessConfig,
1929
2068
  profile: { ...defaultProfile, ...override.profile ?? {} },
1930
2069
  feature: {
1931
- convertFlatSharedInfo: override.feature?.convertFlatSharedInfo ?? false,
1932
- useAutoExternalPooling: override.feature?.useAutoExternalPooling ?? false
2070
+ convertFlatSharedInfo: override.feature?.convertFlatSharedInfo ?? false
1933
2071
  }
1934
2072
  };
1935
2073
  };
@@ -1949,7 +2087,7 @@ var createInitFlow = ({
1949
2087
  adapters,
1950
2088
  config
1951
2089
  }) => {
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) => ({
2090
+ return (remotesOrManifestUrl) => flow.getRemoteEntries(remotesOrManifestUrl).then(flow.processRemoteEntries).then(flow.poolSharedExternals).then(flow.determineSharedExternals).then(flow.decideServingBuilds).then(flow.generateImportMap).then((importMap) => flow.commitChanges(importMap, { override: true })).then(flow.exposeModuleLoader).then((loadRemoteModule) => ({
1953
2091
  config,
1954
2092
  adapters,
1955
2093
  loadRemoteModule
@@ -1960,8 +2098,11 @@ var createInitFlow = ({
1960
2098
  var createInitRemoteEntryFlow = ({
1961
2099
  flow
1962
2100
  }) => {
1963
- const processDynamicRemoteEntry = (remoteEntry) => flow.updateCache(remoteEntry).then(flow.poolDynamicExternals).then(flow.convertToImportMap).then(flow.commitChanges);
1964
- return (remoteEntryUrl, remote) => flow.getRemoteEntry(remoteEntryUrl, remote).then((entry) => entry.map(processDynamicRemoteEntry).orElse(Promise.resolve())).then(() => void 0);
2101
+ const processDynamicRemoteEntry = (remoteEntry) => flow.updateCache(remoteEntry).then(flow.poolDynamicExternals).then(flow.convertToImportMap).then((importMap) => flow.commitChanges(importMap, { markUndecided: true }));
2102
+ return (remoteEntryUrl, remote) => flow.getRemoteEntry(remoteEntryUrl, remote).then((entry) => entry.map(processDynamicRemoteEntry).orElse(Promise.resolve())).then(() => void 0).catch((error) => {
2103
+ const remoteName = typeof remote === "string" ? remote : remote?.name;
2104
+ return flow.rollbackChanges(`[dynamic-init][${remoteName ?? remoteEntryUrl}]`).then(() => Promise.reject(error));
2105
+ });
1965
2106
  };
1966
2107
 
1967
2108
  // src/lib/core/2.app/steps/get-remote-entries.ts
@@ -2068,11 +2209,17 @@ function createGetRemoteEntries(config, ports) {
2068
2209
  }
2069
2210
  }
2070
2211
 
2212
+ // src/lib/utils/compare-strings.ts
2213
+ function compareStrings(a, b) {
2214
+ return a < b ? -1 : a > b ? 1 : 0;
2215
+ }
2216
+
2071
2217
  // src/lib/core/1.domain/externals/basis.ts
2072
2218
  var coverage = (remote) => Object.keys(remote.entries).length;
2073
2219
  function isBetterBasis(candidate, basis) {
2074
2220
  if (basis.cached !== candidate.cached) return candidate.cached;
2075
- return coverage(candidate) > coverage(basis);
2221
+ const byCoverage = coverage(candidate) - coverage(basis);
2222
+ return byCoverage === 0 ? compareStrings(candidate.name, basis.name) < 0 : byCoverage > 0;
2076
2223
  }
2077
2224
  function addRemoteToVersion(version, remote, isHost = false) {
2078
2225
  if (isHost) {
@@ -2096,21 +2243,6 @@ function versionDemands(version) {
2096
2243
  }
2097
2244
  return Array.from(distinct.values());
2098
2245
  }
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
2246
  function forEachVersionEntry(version, accepts, visit) {
2115
2247
  for (const remote of version.remotes) {
2116
2248
  if (remote.servedBy) continue;
@@ -2118,16 +2250,6 @@ function forEachVersionEntry(version, accepts, visit) {
2118
2250
  for (const entrypoint in remote.entries) visit(entrypoint, remote);
2119
2251
  }
2120
2252
  }
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
2253
  function countUncoveredEntrypoints(remote, covered) {
2132
2254
  let uncovered = 0;
2133
2255
  for (const entrypoint in remote.entries) {
@@ -2136,6 +2258,89 @@ function countUncoveredEntrypoints(remote, covered) {
2136
2258
  return uncovered;
2137
2259
  }
2138
2260
 
2261
+ // src/lib/core/1.domain/externals/rows.ts
2262
+ var byTag = (compare) => (a, b) => compare(b.tag, a.tag);
2263
+ var rowAt = (versions, tag, action) => versions.find((v) => v.tag === tag && v.action === action);
2264
+ function mergeRows(versions, lead) {
2265
+ const merged = /* @__PURE__ */ new Map();
2266
+ if (lead) merged.set(`${lead.tag}|${lead.action}`, lead);
2267
+ return versions.filter((v) => {
2268
+ const key = `${v.tag}|${v.action}`;
2269
+ const first = merged.get(key);
2270
+ if (first === v) return true;
2271
+ if (!first) {
2272
+ merged.set(key, v);
2273
+ return true;
2274
+ }
2275
+ first.remotes.push(...v.remotes);
2276
+ return false;
2277
+ });
2278
+ }
2279
+ function findVersionForTag(versions, tag) {
2280
+ let scoped;
2281
+ for (const version of versions) {
2282
+ if (version.tag !== tag) continue;
2283
+ if (version.action !== "scope") return version;
2284
+ scoped ??= version;
2285
+ }
2286
+ return scoped;
2287
+ }
2288
+ function redirectRow(version, tag, acceptsTag) {
2289
+ const objecting = new Set(
2290
+ version.remotes.filter((r) => r.strictVersion && !acceptsTag(tag, version.tag, r.requiredVersion))
2291
+ );
2292
+ if (objecting.size === 0) {
2293
+ version.action = "skip";
2294
+ return [version];
2295
+ }
2296
+ if (objecting.size === version.remotes.length) {
2297
+ version.action = "scope";
2298
+ return [version];
2299
+ }
2300
+ version.remotes = version.remotes.filter((r) => !objecting.has(r));
2301
+ version.action = "skip";
2302
+ return [version, { tag: version.tag, host: false, action: "scope", remotes: [...objecting] }];
2303
+ }
2304
+ function scopeRemotes(versions, remotes) {
2305
+ if (remotes.length === 0) return versions;
2306
+ const turn = new Map(remotes.map((remote, i) => [remote, i]));
2307
+ const moves = /* @__PURE__ */ new Map();
2308
+ versions.forEach((source, at) => {
2309
+ if (source.action === "scope") return;
2310
+ const byTurn = /* @__PURE__ */ new Map();
2311
+ for (const copy of source.remotes) {
2312
+ const i = turn.get(copy.name);
2313
+ if (i === void 0) continue;
2314
+ const copies = byTurn.get(i);
2315
+ if (copies) copies.push(copy);
2316
+ else byTurn.set(i, [copy]);
2317
+ }
2318
+ if (byTurn.size === 0) return;
2319
+ const hostTurn = source.host ? turn.get(source.remotes[0].name) : void 0;
2320
+ source.remotes = source.remotes.filter((r) => !turn.has(r.name));
2321
+ if (hostTurn !== void 0) source.host = false;
2322
+ const tagMoves = moves.get(source.tag) ?? [];
2323
+ for (const [i, copies] of byTurn)
2324
+ tagMoves.push({ turn: i, at, source, copies, host: i === hostTurn });
2325
+ moves.set(source.tag, tagMoves);
2326
+ });
2327
+ const rows = [...versions];
2328
+ for (const [tag, tagMoves] of moves) {
2329
+ tagMoves.sort((a, b) => a.turn - b.turn || a.at - b.at);
2330
+ let scoped = rowAt(rows, tag, "scope");
2331
+ if (!scoped) {
2332
+ scoped = { tag, host: false, action: "scope", remotes: [] };
2333
+ rows.splice(rows.indexOf(tagMoves[0].source) + 1, 0, scoped);
2334
+ }
2335
+ for (const { copies, host } of tagMoves)
2336
+ if (host) {
2337
+ scoped.host = true;
2338
+ scoped.remotes.unshift(...copies);
2339
+ } else scoped.remotes.push(...copies);
2340
+ }
2341
+ return rows.filter((v) => v.remotes.length > 0);
2342
+ }
2343
+
2139
2344
  // src/lib/core/2.app/steps/store-remote-entry.ts
2140
2345
  function createRemoveCachedRemoteEntries(ports) {
2141
2346
  return (remoteNames) => {
@@ -2169,6 +2374,11 @@ function createStoreRemoteEntry(config, ports, logStep) {
2169
2374
  const tag = resolveVersion(remoteEntry, external);
2170
2375
  if (tag === null) return;
2171
2376
  if (external.singleton) {
2377
+ if (external.requiredVersion && !ports.versionCheck.isCompatible(tag, external.requiredVersion))
2378
+ config.log.warn(
2379
+ logStep,
2380
+ `[${remoteEntry.name}][${external.packageName}] requiredVersion '${external.requiredVersion}' excludes its own version '${tag}'; '${tag}' is still accepted for it.`
2381
+ );
2172
2382
  onSharedExternal(remoteEntry, external, sharedExternalContext(remoteEntry, external, tag));
2173
2383
  } else {
2174
2384
  addScopedExternal(remoteEntry, external, tag);
@@ -2219,11 +2429,12 @@ function createStoreRemoteEntry(config, ports, logStep) {
2219
2429
  ...sharedInfo.pool?.trim() ? { pool: sharedInfo.pool } : {},
2220
2430
  entries: sharedInfo.entries
2221
2431
  };
2222
- const cached2 = ports.sharedExternalsRepo.tryGet(sharedInfo.packageName, sharedInfo.shareScope).orElse({ dirty: false, versions: [] });
2432
+ let stored;
2433
+ const readRecord = () => stored ??= ports.sharedExternalsRepo.tryGet(sharedInfo.packageName, sharedInfo.shareScope).get() ?? { dirty: false, versions: [] };
2223
2434
  return {
2224
2435
  tag,
2225
2436
  remote,
2226
- cached: cached2,
2437
+ readRecord,
2227
2438
  scopeType,
2228
2439
  assertSameVersionCompatibility(matchingVersion) {
2229
2440
  if (!remote.strictVersion) return;
@@ -2235,12 +2446,12 @@ function createStoreRemoteEntry(config, ports, logStep) {
2235
2446
  }
2236
2447
  config.log.warn(logStep, errorMsg);
2237
2448
  },
2238
- commit() {
2449
+ write() {
2239
2450
  ports.sharedExternalsRepo.addOrUpdate(
2240
2451
  sharedInfo.packageName,
2241
2452
  {
2242
- dirty: cached2.dirty,
2243
- versions: cached2.versions.sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
2453
+ ...readRecord(),
2454
+ versions: readRecord().versions.sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
2244
2455
  },
2245
2456
  sharedInfo.shareScope
2246
2457
  );
@@ -2284,11 +2495,12 @@ function createProcessRemoteEntries(config, ports) {
2284
2495
  function addSharedExternal(remoteEntry, _sharedInfo, {
2285
2496
  tag,
2286
2497
  remote,
2287
- cached: cached2,
2498
+ readRecord,
2288
2499
  scopeType,
2289
2500
  assertSameVersionCompatibility,
2290
- commit
2501
+ write
2291
2502
  }) {
2503
+ const cached2 = readRecord();
2292
2504
  if (scopeType !== "strict") cached2.dirty = true;
2293
2505
  const matchingVersion = findVersionForTag(cached2.versions, tag);
2294
2506
  if (matchingVersion) {
@@ -2302,158 +2514,74 @@ function createProcessRemoteEntries(config, ports) {
2302
2514
  remotes: [remote]
2303
2515
  });
2304
2516
  }
2305
- commit();
2517
+ write();
2306
2518
  }
2307
2519
  }
2308
2520
 
2309
- // src/lib/core/2.app/steps/apply-winner.ts
2310
- function versionAcceptance(external, isCompatible) {
2521
+ // src/lib/core/1.domain/externals/compatibility.ts
2522
+ function createAcceptsTag(isCompatible, compare) {
2523
+ return (offered, ownTag, requiredVersion) => isCompatible(offered, requiredVersion) || offered === ownTag || compare(offered, ownTag) === 0;
2524
+ }
2525
+ function versionAcceptance(external, acceptsTag) {
2311
2526
  const demands = new Map(
2312
2527
  external.versions.map((v) => [v, versionDemands(v)])
2313
2528
  );
2314
2529
  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))
2530
+ accepts: (version, tag) => demands.get(version).every((d) => acceptsTag(tag, version.tag, d.requiredVersion)),
2531
+ objector: (version, tag) => demands.get(version).find((d) => d.strictVersion && !acceptsTag(tag, version.tag, d.requiredVersion))
2317
2532
  };
2318
2533
  }
2534
+
2535
+ // src/lib/core/2.app/steps/apply-winner.ts
2536
+ function rejectIncompatible(config, copy, rejected, servedAt) {
2537
+ config.log.error(
2538
+ 3,
2539
+ `[${copy.name}][${rejected}] Is not compatible with requiredRange '${copy.requiredVersion}' of shared ${servedAt}.`
2540
+ );
2541
+ throw new NFError(`External ${rejected} could not be shared.`);
2542
+ }
2319
2543
  function createApplyWinner(config) {
2320
- return function applyWinner(externalName, external, winner, isCompatible, acceptance) {
2544
+ return function applyWinner(externalName, external, winner, acceptsTag, acceptance) {
2321
2545
  if (external.versions.length > 1) {
2322
- const { accepts, objector } = acceptance ?? versionAcceptance(external, isCompatible);
2323
2546
  const rebuilt = [];
2324
2547
  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}.`
2548
+ const objector = config.strict.strictExternalCompatibility && acceptance.objector(v, winner.tag);
2549
+ if (objector)
2550
+ rejectIncompatible(
2551
+ config,
2552
+ objector,
2553
+ `${externalName}@${v.tag}`,
2554
+ `${externalName}@${winner.tag}`
2335
2555
  );
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] });
2556
+ rebuilt.push(...redirectRow(v, winner.tag, acceptsTag));
2353
2557
  }
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
- });
2558
+ external.versions = mergeRows(rebuilt, winner);
2364
2559
  }
2365
2560
  winner.action = "share";
2366
- applyEntrypointCoveragePolicy(externalName, external);
2367
2561
  external.dirty = false;
2368
2562
  return external;
2369
2563
  };
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
2564
  }
2428
2565
 
2429
2566
  // src/lib/core/2.app/steps/determine-shared-externals.ts
2430
2567
  function createDetermineSharedExternals(config, ports) {
2431
2568
  const applyWinner = createApplyWinner(config);
2432
2569
  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();
2570
+ const acceptsTag = createAcceptsTag(
2571
+ ports.versionCheck.isCompatible,
2572
+ ports.versionCheck.compare
2573
+ );
2444
2574
  for (const shareScope of ports.sharedExternalsRepo.getScopes()) {
2575
+ if (ports.sharedExternalsRepo.scopeType(shareScope) === "strict") continue;
2445
2576
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
2446
2577
  try {
2447
- const elected = /* @__PURE__ */ new Set();
2448
2578
  Object.entries(sharedExternals).filter(([_, e]) => e.dirty).forEach(([name, external]) => {
2449
2579
  ports.sharedExternalsRepo.addOrUpdate(
2450
2580
  name,
2451
- setVersionActions(name, external, isCompatible),
2581
+ setVersionActions(name, external, acceptsTag),
2452
2582
  shareScope
2453
2583
  );
2454
- elected.add(name);
2455
2584
  });
2456
- if (elected.size > 0) touched.set(shareScope, elected);
2457
2585
  } catch (error) {
2458
2586
  config.log.error(
2459
2587
  3,
@@ -2471,24 +2599,22 @@ function createDetermineSharedExternals(config, ports) {
2471
2599
  );
2472
2600
  }
2473
2601
  }
2474
- return Promise.resolve(touched);
2602
+ return Promise.resolve();
2475
2603
  };
2476
2604
  function uncoveredTears(external, winner, accepts) {
2477
- const basis = versionEntries(winner);
2605
+ const covered = /* @__PURE__ */ new Set();
2606
+ forEachVersionEntry(winner, void 0, (entrypoint) => covered.add(entrypoint));
2478
2607
  return external.versions.reduce((sum, v) => {
2479
2608
  if (v === winner) return sum;
2480
2609
  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
- );
2610
+ return v.remotes.reduce((n, r) => n + countUncoveredEntrypoints(r, covered), sum);
2485
2611
  }, 0);
2486
2612
  }
2487
- function setVersionActions(externalName, external, isCompatible) {
2613
+ function setVersionActions(externalName, external, acceptsTag) {
2614
+ const acceptance = versionAcceptance(external, acceptsTag);
2488
2615
  if (external.versions.length === 1) {
2489
- return applyWinner(externalName, external, external.versions[0], isCompatible);
2616
+ return applyWinner(externalName, external, external.versions[0], acceptsTag, acceptance);
2490
2617
  }
2491
- const acceptance = versionAcceptance(external, isCompatible);
2492
2618
  const { accepts } = acceptance;
2493
2619
  let sharedVersion = external.versions.find((v) => v.host);
2494
2620
  if (!sharedVersion && config.profile.latestSharedExternal) {
@@ -2513,14 +2639,12 @@ function createDetermineSharedExternals(config, ports) {
2513
2639
  return [v, [...groups.values()]];
2514
2640
  })
2515
2641
  );
2516
- const costOf = (version, tag) => selfServing.get(version).reduce((n, g) => isCompatible(tag, g.requiredVersion) ? n : n + g.copies, 0);
2642
+ const costOf = (version, tag) => selfServing.get(version).reduce(
2643
+ (n, g) => acceptsTag(tag, version.tag, g.requiredVersion) ? n : n + g.copies,
2644
+ 0
2645
+ );
2517
2646
  external.versions.forEach((vA) => {
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
- );
2647
+ const extraDownloads = external.versions.reduce((sum, vB) => sum + costOf(vB, vA.tag), 0);
2524
2648
  if (extraDownloads < leastExtraDownloads) {
2525
2649
  leastExtraDownloads = extraDownloads;
2526
2650
  leastTears = uncoveredTears(external, vA, accepts);
@@ -2538,564 +2662,582 @@ function createDetermineSharedExternals(config, ports) {
2538
2662
  if (!sharedVersion) {
2539
2663
  throw new NFError(`[${externalName}] Could not determine shared version!`);
2540
2664
  }
2541
- return applyWinner(externalName, external, sharedVersion, isCompatible, acceptance);
2665
+ return applyWinner(externalName, external, sharedVersion, acceptsTag, acceptance);
2542
2666
  }
2543
2667
  }
2544
2668
 
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 };
2669
+ // src/lib/core/1.domain/externals/specifier.ts
2670
+ function owningPackage(name) {
2671
+ const depth = name.startsWith("@") ? 2 : 1;
2672
+ let cut = -1;
2673
+ for (let seen = 0; seen < depth; seen++) {
2674
+ cut = name.indexOf("/", cut + 1);
2675
+ if (cut === -1) return void 0;
2676
+ }
2677
+ return name.slice(0, cut);
2556
2678
  }
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
- }
2580
- }
2581
- }
2679
+ var packageOf = (name) => owningPackage(name) ?? name;
2680
+ var SpecifierTags = class extends Map {
2681
+ packages = /* @__PURE__ */ new Map();
2682
+ constructor(entries = []) {
2683
+ super();
2684
+ for (const [specifier, tag] of entries) this.set(specifier, tag);
2685
+ }
2686
+ set(specifier, tag) {
2687
+ super.set(specifier, tag);
2688
+ const pkg = owningPackage(specifier);
2689
+ if (pkg === void 0) this.packages.set(specifier, tag);
2690
+ else if (!this.packages.has(pkg)) this.packages.set(pkg, tag);
2691
+ return this;
2692
+ }
2693
+ clear() {
2694
+ super.clear();
2695
+ this.packages.clear();
2696
+ }
2697
+ pinnedTag(specifier) {
2698
+ return this.get(specifier) ?? this.packages.get(owningPackage(specifier) ?? specifier);
2582
2699
  }
2583
- return builds;
2700
+ };
2701
+ function groupByPackage(items, nameOf) {
2702
+ const packages = /* @__PURE__ */ new Map();
2703
+ for (const item of items) {
2704
+ const pkg = packageOf(nameOf(item));
2705
+ const group = packages.get(pkg);
2706
+ if (group) group.push(item);
2707
+ else packages.set(pkg, [item]);
2708
+ }
2709
+ return packages;
2584
2710
  }
2585
- function ownCopies(members, only) {
2586
- const own = /* @__PURE__ */ new Map();
2711
+
2712
+ // src/lib/core/1.domain/pooling/builds.ts
2713
+ function copiesByRemote(members) {
2714
+ const shipped = /* @__PURE__ */ new Map();
2587
2715
  for (const member of members) {
2588
- for (const version of member.external.versions) {
2716
+ const seen = /* @__PURE__ */ new Set();
2717
+ for (const version of member.external.versions)
2589
2718
  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 });
2719
+ if (seen.has(meta.name)) continue;
2720
+ seen.add(meta.name);
2721
+ let copies = shipped.get(meta.name);
2722
+ if (!copies) shipped.set(meta.name, copies = []);
2723
+ copies.push({
2724
+ member: member.name,
2725
+ tag: version.tag,
2726
+ requiredVersion: meta.requiredVersion,
2727
+ strict: meta.strictVersion,
2728
+ host: version.host && meta === version.remotes[0],
2729
+ specifiers: Object.keys(meta.entries)
2730
+ });
2595
2731
  }
2596
- }
2597
2732
  }
2598
- return own;
2733
+ return shipped;
2599
2734
  }
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;
2735
+ var specifierCount = (copies) => copies.reduce((n, copy) => n + copy.specifiers.length, 0);
2736
+ function buildOf(owner, copies) {
2737
+ const tags = new SpecifierTags();
2738
+ const tagByMember = /* @__PURE__ */ new Map();
2739
+ for (const copy of copies) {
2740
+ for (const specifier of copy.specifiers)
2741
+ if (!tags.has(specifier)) tags.set(specifier, copy.tag);
2742
+ tagByMember.set(copy.member, copy.tag);
2743
+ }
2744
+ return { owner, tags, tagByMember, host: copies.some((c) => c.host) };
2615
2745
  }
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
- }
2746
+
2747
+ // src/lib/core/1.domain/pooling/rules.ts
2748
+ function agrees(copies, tags) {
2749
+ for (const copy of copies)
2750
+ for (const s of copy.specifiers) {
2751
+ const tag = tags.pinnedTag(s);
2752
+ if (tag !== void 0 && tag !== copy.tag) return false;
2628
2753
  }
2629
- }
2630
- return consumed;
2754
+ return true;
2631
2755
  }
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
- }
2756
+ function serves(copies, tags, acceptsTag) {
2757
+ for (const copy of copies)
2758
+ for (const s of copy.specifiers) {
2759
+ const tag = tags.get(s);
2760
+ if (tag === void 0 || !acceptsTag(tag, copy.tag, copy.requiredVersion)) return false;
2642
2761
  }
2643
- }
2644
- for (const specifier of wanted) if (!served.has(specifier)) return false;
2645
2762
  return true;
2646
2763
  }
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;
2764
+ function unwitnessedGap(copies, tags, builds) {
2765
+ const specifiers = copies.flatMap((c) => c.specifiers);
2766
+ let gap = 0;
2767
+ for (const build of builds) {
2768
+ const miss = specifiers.findIndex((s) => build.tags.pinnedTag(s) !== tags.get(s));
2769
+ if (miss === -1) return void 0;
2770
+ gap = Math.max(gap, miss);
2771
+ }
2772
+ return specifiers[gap];
2655
2773
  }
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;
2774
+
2775
+ // src/lib/core/1.domain/pooling/election.ts
2776
+ function elect(input) {
2777
+ const shipped = copiesByRemote(input.members);
2778
+ const builds = /* @__PURE__ */ new Map();
2779
+ for (const [owner, copies] of shipped) builds.set(owner, buildOf(owner, copies));
2780
+ const pool = {
2781
+ ...input,
2782
+ shipped,
2783
+ builds,
2784
+ packages: new Map([...shipped].map(([owner, copies]) => [owner, packageCopies(owner, copies)])),
2785
+ remotes: [...shipped.keys()],
2786
+ servesAt: (remote, tags) => serves(shipped.get(remote), tags, input.acceptsTag),
2787
+ agreesAt: (remote, tags) => agrees(shipped.get(remote), tags)
2788
+ };
2789
+ const hostRemote = pool.remotes.find((r) => builds.get(r).host);
2790
+ const state = roundOne(pool, hostRemote);
2791
+ formSubpools(pool, state);
2792
+ addExtensions(pool, state);
2793
+ promote(pool, state);
2794
+ while (settle(pool, state)) formSubpools(pool, state);
2795
+ const firstSeen = new Map(pool.remotes.map((r, i) => [r, i]));
2796
+ const self = state.pending.sort((a, b) => firstSeen.get(a) - firstSeen.get(b));
2797
+ return {
2798
+ winner: state.winner,
2799
+ winnerIsHost: hostRemote !== void 0,
2800
+ globalTags: state.globalTags,
2801
+ ...placeRemotes(state.globalRemotes, state.subpools, self, (r) => missOf(pool, state, r))
2802
+ };
2667
2803
  }
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);
2804
+ function newer(pool, a, b) {
2805
+ for (const { name } of pool.members) {
2806
+ const tagA = a.tagByMember.get(name);
2807
+ const tagB = b.tagByMember.get(name);
2808
+ if (tagA === void 0 || tagB === void 0) continue;
2809
+ const d = pool.compare(tagA, tagB);
2810
+ if (d !== 0) return d;
2811
+ }
2812
+ return 0;
2813
+ }
2814
+ function rank(pool, candidates, keys) {
2815
+ const scored = [...candidates].sort((a, b) => compareStrings(a.build.owner, b.build.owner)).map((c) => ({ c, k: keys.map((key) => key(c)) }));
2816
+ scored.sort((a, b) => {
2817
+ for (let i = 0; i < keys.length; i++) {
2818
+ const d = b.k[i] - a.k[i];
2819
+ if (d !== 0) return d;
2673
2820
  }
2674
- }
2675
- return hosts;
2821
+ return newer(pool, b.c.build, a.c.build) || compareStrings(a.c.build.owner, b.c.build.owner);
2822
+ });
2823
+ return scored[0]?.c;
2676
2824
  }
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
- }
2825
+ function roundOne(pool, hostRemote) {
2826
+ const { remotes } = pool;
2827
+ const scored = /* @__PURE__ */ new Map();
2828
+ const round1 = (hostRemote ? [hostRemote] : remotes).map((owner) => {
2829
+ const build = pool.builds.get(owner);
2830
+ const key = JSON.stringify([[...build.tags], [...build.tagByMember]]);
2831
+ let score = scored.get(key);
2832
+ if (!score) {
2833
+ const served = new Set(remotes.filter((r) => pool.servesAt(r, build.tags)));
2834
+ const agreeing = remotes.filter((r) => !served.has(r) && pool.agreesAt(r, build.tags)).length;
2835
+ scored.set(key, score = { served: [...served], agreeing });
2684
2836
  }
2837
+ return { build, ...score };
2838
+ });
2839
+ const newerThan = (c) => round1.filter((o) => newer(pool, c.build, o.build) > 0).length;
2840
+ const round1Winner = rank(pool, round1, [
2841
+ ...pool.latestFirst ? [newerThan] : [],
2842
+ (c) => c.served.length,
2843
+ (c) => c.agreeing,
2844
+ (c) => c.build.owner === pool.previousWinner ? 1 : 0
2845
+ ]);
2846
+ const winner = round1Winner.build.owner;
2847
+ const globalTags = new SpecifierTags(round1Winner.build.tags);
2848
+ const globalRemotes = /* @__PURE__ */ new Set([winner, ...round1Winner.served]);
2849
+ const pending = remotes.filter((r) => !globalRemotes.has(r));
2850
+ return { winner, globalTags, globalRemotes, extensions: /* @__PURE__ */ new Set(), subpools: [], pending };
2851
+ }
2852
+ function formSubpools(pool, state) {
2853
+ const servable = new Map(
2854
+ state.pending.map((owner) => {
2855
+ const build = pool.builds.get(owner);
2856
+ const served = state.pending.filter((r) => pool.servesAt(r, build.tags));
2857
+ return [owner, { build, served }];
2858
+ })
2859
+ );
2860
+ for (; ; ) {
2861
+ const stillPending = new Set(state.pending);
2862
+ const candidates = state.pending.map((owner) => {
2863
+ const { build, served } = servable.get(owner);
2864
+ return { build, served: served.filter((r) => stillPending.has(r)) };
2865
+ }).filter((c) => c.served.includes(c.build.owner) && c.served.length >= 2);
2866
+ const best = rank(pool, candidates, [(c) => c.served.length]);
2867
+ if (!best) break;
2868
+ state.subpools.push({ owner: best.build.owner, remotes: best.served });
2869
+ state.pending = state.pending.filter((r) => !best.served.includes(r));
2685
2870
  }
2686
- return arrival;
2687
2871
  }
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);
2872
+ function promote(pool, state) {
2873
+ const { globalTags, globalRemotes } = state;
2874
+ const takesGlobal = (remote) => pool.servesAt(remote, globalTags) && unwitnessedGap(pool.shipped.get(remote), globalTags, pool.builds.values()) === void 0;
2875
+ for (const remote of state.pending) if (takesGlobal(remote)) globalRemotes.add(remote);
2876
+ state.pending = state.pending.filter((r) => !globalRemotes.has(r));
2877
+ for (const subpool of state.subpools) {
2878
+ const moved = subpool.remotes.filter((r) => r !== subpool.owner && takesGlobal(r));
2879
+ for (const remote of moved) globalRemotes.add(remote);
2880
+ subpool.remotes = subpool.remotes.filter((r) => !moved.includes(r));
2881
+ }
2882
+ state.subpools = state.subpools.filter((subpool) => {
2883
+ if (subpool.remotes.length >= 2) return true;
2884
+ if (takesGlobal(subpool.owner)) globalRemotes.add(subpool.owner);
2885
+ else state.pending.push(subpool.owner);
2886
+ return false;
2692
2887
  });
2693
- return shared;
2694
2888
  }
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
- }
2889
+ function addExtensions(pool, state) {
2890
+ const { globalTags, globalRemotes } = state;
2891
+ const runsOn = (remote) => state.subpools.find((p) => p.remotes.includes(remote))?.owner ?? remote;
2892
+ const agreeing = pool.remotes.filter((r) => !globalRemotes.has(r) && pool.agreesAt(r, globalTags));
2893
+ const runsGlobally = (remote) => pool.agreesAt(runsOn(remote), globalTags);
2894
+ const offered = offers(pool, globalTags, agreeing.filter(runsGlobally));
2895
+ const fill = pool.builds.get(state.winner).host ? agreeing.filter((r) => !runsGlobally(r)) : [];
2896
+ for (const [pkg, offer] of offers(pool, globalTags, fill))
2897
+ if (!offered.has(pkg)) offered.set(pkg, offer);
2898
+ for (const [pkg, offer] of offered) {
2899
+ if (!offer) continue;
2900
+ state.extensions.add(pkg);
2901
+ for (const s of offer.specifiers) globalTags.set(s, offer.tag);
2705
2902
  }
2706
2903
  }
2707
-
2708
- // src/lib/core/2.app/steps/pooling/anchoring.ts
2709
- function acceptanceTable(members, isCompatible) {
2710
- const table = /* @__PURE__ */ new Map();
2711
- for (const member of members) {
2712
- const tags = [];
2713
- for (const version of member.external.versions) tags.push(version.tag);
2714
- for (const version of member.external.versions) {
2715
- for (const meta of version.remotes) {
2716
- let byMember = table.get(meta.name);
2717
- if (!byMember) table.set(meta.name, byMember = /* @__PURE__ */ new Map());
2718
- if (byMember.has(member.name)) continue;
2719
- const accepted = /* @__PURE__ */ new Set();
2720
- for (let t = 0; t < tags.length; t++) {
2721
- if (isCompatible(tags[t], meta.requiredVersion)) accepted.add(tags[t]);
2722
- }
2723
- byMember.set(member.name, accepted);
2724
- }
2904
+ function packageCopies(owner, copies) {
2905
+ const packages = /* @__PURE__ */ new Map();
2906
+ for (const copy of copies)
2907
+ for (const s of copy.specifiers) {
2908
+ const pkg = packageOf(s);
2909
+ const seen = packages.get(pkg);
2910
+ if (seen === void 0) packages.set(pkg, { owner, tag: copy.tag, specifiers: /* @__PURE__ */ new Set([s]) });
2911
+ else if (seen && seen.tag !== copy.tag) packages.set(pkg, null);
2912
+ else seen?.specifiers.add(s);
2725
2913
  }
2726
- }
2727
- return table;
2914
+ return packages;
2728
2915
  }
2729
- function covers(coverage2, consumed) {
2730
- for (const specifier of consumed) if (!coverage2.has(specifier)) return false;
2731
- return true;
2916
+ function offers(pool, globalTags, remotes) {
2917
+ const candidates = /* @__PURE__ */ new Map();
2918
+ for (const owner of remotes)
2919
+ for (const [pkg, copy] of pool.packages.get(owner))
2920
+ if (globalTags.pinnedTag(pkg) === void 0)
2921
+ candidates.set(pkg, [...candidates.get(pkg) ?? [], copy]);
2922
+ const offered = /* @__PURE__ */ new Map();
2923
+ for (const [pkg, copies] of candidates)
2924
+ offered.set(pkg, oneTag(copies) ? coveringCopy(copies) : null);
2925
+ return offered;
2732
2926
  }
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;
2927
+ var oneTag = (copies) => copies.every((c) => c !== null && c.tag === copies[0].tag);
2928
+ function coveringCopy(candidates) {
2929
+ const scored = candidates.map((c) => ({
2930
+ c,
2931
+ covers: candidates.filter((o) => contains(c.specifiers, o.specifiers)).length
2932
+ }));
2933
+ scored.sort(
2934
+ (a, b) => b.covers - a.covers || b.c.specifiers.size - a.c.specifiers.size || compareStrings(a.c.owner, b.c.owner)
2935
+ );
2936
+ return scored[0].c;
2742
2937
  }
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;
2938
+ var contains = (a, b) => [...b].every((s) => a.has(s));
2939
+ function settle(pool, state) {
2940
+ const { globalTags, globalRemotes, extensions } = state;
2941
+ let demoted = false;
2942
+ for (; ; ) {
2943
+ const dropped = new Set([...extensions].filter((pkg) => !publishedWhole(pool, state, pkg)));
2944
+ if (dropped.size === 0) return demoted;
2945
+ for (const pkg of dropped) extensions.delete(pkg);
2946
+ const kept = [...globalTags].filter(([s]) => !dropped.has(packageOf(s)));
2947
+ globalTags.clear();
2948
+ for (const [s, tag] of kept) globalTags.set(s, tag);
2949
+ for (const remote of globalRemotes)
2950
+ if (remote !== state.winner && !pool.servesAt(remote, globalTags)) {
2951
+ globalRemotes.delete(remote);
2952
+ const subpool = state.subpools.find(
2953
+ (p) => pool.servesAt(remote, pool.builds.get(p.owner).tags)
2954
+ );
2955
+ if (subpool) subpool.remotes.push(remote);
2956
+ else state.pending.push(remote);
2957
+ demoted = true;
2752
2958
  }
2753
- }
2754
- if (matches2) return true;
2755
2959
  }
2756
- return false;
2757
2960
  }
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;
2961
+ function publishedWhole(pool, state, pkg) {
2962
+ const published = new Set([...state.globalTags.keys()].filter((s) => packageOf(s) === pkg));
2963
+ const tag = state.globalTags.pinnedTag(pkg);
2964
+ return [...state.globalRemotes].some((remote) => {
2965
+ const copy = pool.packages.get(remote).get(pkg);
2966
+ return !!copy && copy.tag === tag && copy.specifiers.size === published.size && contains(copy.specifiers, published);
2967
+ });
2968
+ }
2969
+ function missOf(pool, state, remote) {
2970
+ const { globalTags } = state;
2971
+ const copies = pool.shipped.get(remote);
2972
+ let rejected;
2973
+ for (const copy of copies)
2974
+ for (const s of copy.specifiers) {
2975
+ const tag = globalTags.get(s);
2976
+ if (tag === void 0 || pool.acceptsTag(tag, copy.tag, copy.requiredVersion)) continue;
2977
+ if (copy.strict) return { cause: "incompatible", member: copy.member, tag, strict: true };
2978
+ rejected ??= { cause: "incompatible", member: copy.member, tag, strict: false };
2770
2979
  }
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 };
2980
+ if (rejected) return rejected;
2981
+ for (const copy of copies)
2982
+ for (const s of copy.specifiers) if (!globalTags.has(s)) return { cause: "uncovered", gap: s };
2983
+ const gap = unwitnessedGap(copies, globalTags, pool.builds.values());
2984
+ if (gap) return { cause: "uncovered", gap, unshipped: true };
2985
+ if (state.subpools.some((p) => p.owner === remote)) return void 0;
2986
+ throw new Error(`'${remote}' missed the global map with nothing it rejects or lacks.`);
2777
2987
  }
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;
2988
+ function previousWinner(members) {
2989
+ const winners = new Set(members.flatMap((m) => m.external.poolWinner ?? []));
2990
+ const [winner] = winners;
2991
+ if (winners.size !== 1) return void 0;
2992
+ const ships = members.some(
2993
+ (m) => m.external.versions.some((v) => v.remotes.some((r) => r.name === winner))
2994
+ );
2995
+ return ships ? winner : void 0;
2787
2996
  }
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;
2997
+ function placeRemotes(globalRemotes, subpools, self, missOf2) {
2998
+ const misses = /* @__PURE__ */ new Map();
2999
+ for (const remote of [...subpools.flatMap((p) => p.remotes), ...self])
3000
+ misses.set(remote, missOf2(remote));
3001
+ const placements = /* @__PURE__ */ new Map();
3002
+ for (const remote of globalRemotes) placements.set(remote, { kind: "global" });
3003
+ for (const { owner, remotes } of subpools)
3004
+ for (const remote of remotes) placements.set(remote, { kind: "subpool", owner });
3005
+ for (const remote of self)
3006
+ placements.set(remote, { kind: "self", cause: misses.get(remote).cause });
3007
+ return { placements, misses };
2837
3008
  }
2838
3009
 
2839
- // src/lib/core/2.app/steps/pooling/pool-graph.ts
2840
- var SCOPED_PACKAGE = /^@([^/]+)\//;
2841
- function createDSU() {
2842
- const ids = /* @__PURE__ */ new Map();
2843
- const parent = [];
2844
- const size = [];
2845
- const intern = (key) => {
2846
- let id = ids.get(key);
2847
- if (id === void 0) {
2848
- id = parent.length;
2849
- ids.set(key, id);
2850
- parent.push(id);
2851
- size.push(1);
2852
- }
2853
- return id;
2854
- };
2855
- const findRoot = (x) => {
2856
- while (parent[x] !== x) {
2857
- parent[x] = parent[parent[x]];
2858
- x = parent[x];
3010
+ // src/lib/core/1.domain/pooling/membership.ts
3011
+ function unionFind() {
3012
+ const parent = /* @__PURE__ */ new Map();
3013
+ const find = (node) => {
3014
+ for (let up = parent.get(node); up !== void 0; up = parent.get(node)) {
3015
+ const grand = parent.get(up);
3016
+ if (grand === void 0) return up;
3017
+ parent.set(node, grand);
3018
+ node = grand;
2859
3019
  }
2860
- return x;
3020
+ return node;
2861
3021
  };
2862
- return {
2863
- union(a, b) {
2864
- let rootA = findRoot(intern(a));
2865
- let rootB = findRoot(intern(b));
2866
- if (rootA === rootB) return;
2867
- if (size[rootA] < size[rootB]) [rootA, rootB] = [rootB, rootA];
2868
- parent[rootB] = rootA;
2869
- size[rootA] = size[rootA] + size[rootB];
2870
- },
2871
- // Interns the key if unseen — an isolated key is its own component.
2872
- component(key) {
2873
- return findRoot(intern(key));
2874
- }
3022
+ const union = (a, b) => {
3023
+ const rootA = find(a);
3024
+ const rootB = find(b);
3025
+ if (rootA !== rootB) parent.set(rootB, rootA);
2875
3026
  };
3027
+ return { find, union };
2876
3028
  }
2877
3029
  var extNode = (name) => `ext\0${name}`;
2878
- var tagNode = (remote, tag) => `tag\0${remote}\0${tag}`;
2879
- var scopeNode = (remote, scope) => `scope\0${remote}\0${scope}`;
2880
- function autoScope(name, useAutoExternalPooling) {
2881
- return useAutoExternalPooling ? SCOPED_PACKAGE.exec(name)?.[1] : void 0;
3030
+ var labelNode = (label) => `label\0${label}`;
3031
+ var labelOf = (pool) => pool?.trim() || void 0;
3032
+ function labelsIn(sharedExternals) {
3033
+ const labelsOf = /* @__PURE__ */ new Map();
3034
+ for (const [name, external] of Object.entries(sharedExternals))
3035
+ labelsOf.set(
3036
+ name,
3037
+ external.versions.flatMap((v) => v.remotes.flatMap((r) => labelOf(r.pool) ?? []))
3038
+ );
3039
+ return labelsOf;
2882
3040
  }
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;
3041
+ function labelledComponents(labelsOf) {
3042
+ const { find, union } = unionFind();
3043
+ for (const [name, labels] of labelsOf)
3044
+ for (const label of labels) union(extNode(name), labelNode(label));
3045
+ for (const name of labelsOf.keys()) {
3046
+ const owner = owningPackage(name);
3047
+ if (owner !== void 0) union(extNode(name), extNode(owner));
2889
3048
  }
2890
- return name.slice(0, cut);
3049
+ const byComponent = /* @__PURE__ */ new Map();
3050
+ for (const name of labelsOf.keys()) {
3051
+ const root = find(extNode(name));
3052
+ const names = byComponent.get(root) ?? byComponent.set(root, []).get(root);
3053
+ names.push(name);
3054
+ }
3055
+ return [...byComponent.values()].filter((names) => names.some((n) => labelsOf.get(n).length > 0));
2891
3056
  }
2892
- function groupByMembership(candidates, log) {
2893
- const dsu = createDSU();
2894
- const tagged = /* @__PURE__ */ new Set();
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
- }
2901
- for (const candidate of candidates) {
2902
- for (const edge of candidate.tags) {
2903
- dsu.union(extNode(candidate.name), tagNode(edge.remote, edge.tag));
2904
- tagged.add(candidate.name);
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);
3057
+ function buildPools(sharedExternals) {
3058
+ const labelsOf = labelsIn(sharedExternals);
3059
+ const pools = /* @__PURE__ */ new Map();
3060
+ const labelledAlone = [];
3061
+ for (const names of labelledComponents(labelsOf)) {
3062
+ if (names.length < 2) labelledAlone.push(names[0]);
3063
+ else {
3064
+ const members = names.map((name) => ({ name, external: sharedExternals[name] }));
3065
+ pools.set(
3066
+ mostDeclaredLabel(members, labelsOf),
3067
+ members.sort((a, b) => compareStrings(a.name, b.name))
3068
+ );
2912
3069
  }
2913
3070
  }
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));
2919
- }
2920
- const byComponent = /* @__PURE__ */ new Map();
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 });
2925
- }
2926
- const pools = [];
2927
- for (const members of byComponent.values()) {
2928
- if (!members.some((m) => joined.has(m.name))) continue;
2929
- members.sort((a, b) => a.name.localeCompare(b.name));
2930
- if (members.length < 2) {
2931
- const only = members[0];
2932
- if (tagged.has(only.name)) {
2933
- log?.warn(
2934
- 3,
2935
- `[${only.name}] declares a 'pool' tag but no other external joined its pool; likely a typo or a missing sibling.`
2936
- );
2937
- }
2938
- continue;
2939
- }
2940
- pools.push(members);
2941
- }
2942
- pools.sort((a, b) => a[0].name.localeCompare(b[0].name));
2943
- return new Map(pools.map((members) => [members[0].name, members.map((m) => m.value)]));
3071
+ return { pools, labelledAlone };
2944
3072
  }
2945
- function buildPools(sharedExternals, useAutoExternalPooling, log) {
2946
- const candidates = Object.entries(sharedExternals).map(
2947
- ([name, external]) => ({
2948
- name,
2949
- scope: autoScope(name, useAutoExternalPooling),
2950
- tags: external.versions.flatMap(
2951
- (v) => v.remotes.flatMap((r) => {
2952
- const tag = r.pool?.trim();
2953
- return tag ? [{ remote: r.name, tag }] : [];
2954
- })
2955
- ),
2956
- remotes: external.versions.flatMap((v) => v.remotes.map((r) => r.name)),
2957
- value: { name, external }
2958
- })
3073
+ function pooledPackagesWith(sharedExternals, copies) {
3074
+ const labelsOf = labelsIn(sharedExternals);
3075
+ for (const { name, pool } of copies) {
3076
+ const labels = labelsOf.get(name) ?? [];
3077
+ const label = labelOf(pool);
3078
+ labelsOf.set(name, label === void 0 ? labels : [...labels, label]);
3079
+ }
3080
+ return new Set(
3081
+ labelledComponents(labelsOf).filter((names) => names.length > 1).flatMap((names) => names.map(packageOf))
2959
3082
  );
2960
- return groupByMembership(candidates, log);
3083
+ }
3084
+ function mostDeclaredLabel(members, labelsOf) {
3085
+ const counts = /* @__PURE__ */ new Map();
3086
+ for (const member of members)
3087
+ for (const label of labelsOf.get(member.name)) counts.set(label, (counts.get(label) ?? 0) + 1);
3088
+ let best = "";
3089
+ let top = 0;
3090
+ for (const [label, count] of counts)
3091
+ if (count > top || count === top && compareStrings(label, best) < 0)
3092
+ [best, top] = [label, count];
3093
+ return best;
2961
3094
  }
2962
3095
 
2963
- // src/lib/core/2.app/steps/pooling/pool.util.ts
2964
- function lazy(make) {
2965
- let value;
2966
- return () => value ??= make();
3096
+ // src/lib/core/1.domain/pooling/pool-state.ts
3097
+ function hasPoolResults(external) {
3098
+ if (external.poolName !== void 0 || external.poolWinner !== void 0) return true;
3099
+ return external.versions.some(
3100
+ (v) => v.remotes.some((r) => r.servedBy !== void 0 || r.poolCause !== void 0)
3101
+ );
2967
3102
  }
2968
- function remotesInPool(members) {
2969
- return [
2970
- ...new Set(members.flatMap((m) => m.external.versions.flatMap((v) => v.remotes.map((r) => r.name))))
2971
- ];
3103
+ function scopeHasPoolState(scope) {
3104
+ for (const name in scope) {
3105
+ const external = scope[name];
3106
+ if (hasPoolResults(external)) return true;
3107
+ for (const version of external.versions)
3108
+ for (const remote of version.remotes) if (remote.pool?.trim()) return true;
3109
+ }
3110
+ return false;
2972
3111
  }
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
- )
3112
+
3113
+ // src/lib/core/1.domain/pooling/records.ts
3114
+ function memberRecord(member, pool, compare) {
3115
+ const { globalTags, placements } = pool;
3116
+ let sharedTag = globalTags.get(member.name);
3117
+ for (const version of member.external.versions)
3118
+ for (const meta of version.remotes)
3119
+ for (const s in meta.entries) sharedTag ??= globalTags.get(s);
3120
+ const claimsGlobally = (name) => placements.get(name).kind === "global";
3121
+ const placed = [];
3122
+ let winnerRow;
3123
+ const place = (tag, action, meta) => {
3124
+ const row = { tag, host: false, action, remotes: [meta] };
3125
+ placed.push(row);
3126
+ if (meta.name !== pool.winner) return;
3127
+ winnerRow = row;
3128
+ row.host = pool.winnerIsHost;
2980
3129
  };
3130
+ for (const version of member.external.versions)
3131
+ for (const stored of version.remotes) {
3132
+ const { servedBy: _servedBy, poolCause: _poolCause, ...rest } = stored;
3133
+ const meta = { ...rest, cached: false };
3134
+ const placement = placements.get(meta.name);
3135
+ if (placement.kind === "global")
3136
+ place(version.tag, version.tag === sharedTag ? "share" : "skip", meta);
3137
+ else if (placement.kind === "subpool")
3138
+ place(version.tag, "skip", { ...meta, servedBy: placement.owner });
3139
+ else place(version.tag, "scope", { ...meta, poolCause: placement.cause });
3140
+ }
3141
+ const rows = mergeRows(placed, winnerRow);
3142
+ for (const row of rows)
3143
+ row.remotes.sort((a, b) => Number(!claimsGlobally(a.name)) - Number(!claimsGlobally(b.name)));
3144
+ const order = { share: 0, skip: 1, scope: 2 };
3145
+ const newest = byTag(compare);
3146
+ const versions = rows.sort((a, b) => newest(a, b) || order[a.action] - order[b.action]);
3147
+ return { dirty: false, poolName: pool.poolName, versions };
2981
3148
  }
2982
-
2983
- // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
2984
- function islandedRemotes(members) {
2985
- const islanded = /* @__PURE__ */ new Map();
2986
- for (const member of members)
2987
- for (const version of member.external.versions)
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
- }
2997
- return islanded;
3149
+ function movedRecord(external, remote, move, compare) {
3150
+ return withoutStaleServingBuild(moveCopy(external, remote, move, compare));
2998
3151
  }
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;
3152
+ function withoutStaleServingBuild(record) {
3153
+ const { servingBuild, ...rest } = record;
3154
+ if (servingBuild === void 0) return record;
3155
+ const shares = record.versions.some(
3156
+ (v) => v.action === "share" && v.remotes.some((r) => r.name === servingBuild)
3157
+ );
3158
+ return shares ? record : rest;
3013
3159
  }
3014
- var anyAnchorStored = (members) => members.some((m) => m.external.versions.some((v) => v.remotes.some((r) => r.servedBy !== void 0)));
3015
- function findTornRemotes(members, islanded, basis, served, hosts) {
3016
- const { suspect, selfServed } = movedRemotes(members, islanded, basis, served, hosts);
3017
- if (suspect.size === 0) return [];
3018
- const builds = liveBuilds(members, islanded);
3019
- const shared = sharedTagPerSpecifier(members, islanded);
3020
- const own = ownCopies(members, suspect);
3021
- const torn = [];
3022
- for (const remote of suspect) {
3023
- const copies = own.get(remote) ?? [];
3024
- const mine = selfServed.get(remote);
3025
- const anchors = served.get(remote);
3026
- const ownTags = /* @__PURE__ */ new Map();
3027
- const resolved = /* @__PURE__ */ new Map();
3028
- for (const copy of copies) {
3029
- const from = mine?.has(copy.member) ? remote : anchors?.get(copy.member);
3030
- for (const specifier in copy.entries) {
3031
- if (!ownTags.has(specifier)) ownTags.set(specifier, copy.tag);
3032
- const tag = from === remote ? copy.tag : from !== void 0 ? builds.get(from)?.tags.get(specifier) : shared.get(specifier);
3033
- if (tag !== void 0 && !resolved.has(specifier)) resolved.set(specifier, tag);
3034
- }
3035
- }
3036
- if (resolved.size === 0) continue;
3037
- if (ships(ownTags, resolved)) continue;
3038
- let witnessed = false;
3039
- for (const [, build] of builds) {
3040
- if (ships(build.tags, resolved)) {
3041
- witnessed = true;
3042
- break;
3043
- }
3044
- }
3045
- if (!witnessed) {
3046
- torn.push({
3047
- remote,
3048
- combination: [...resolved].map(([specifier, tag]) => `${specifier}@${tag}`).join(", ")
3049
- });
3050
- }
3051
- }
3052
- return torn;
3160
+ function moveCopy(external, remote, move, compare) {
3161
+ if ("fromMap" in move) return recordFromMap(external, remote);
3162
+ const moved = [];
3163
+ const versions = external.versions.map((v) => {
3164
+ const copy = v.remotes.find((r) => r.name === remote);
3165
+ if (!copy) return v;
3166
+ const { servedBy: _servedBy, ...rest } = copy;
3167
+ const meta = { ...rest, cached: true, poolCause: move.cause };
3168
+ if (v.action === "scope")
3169
+ return { ...v, remotes: v.remotes.map((r) => r === copy ? meta : r) };
3170
+ moved.push({ tag: v.tag, meta });
3171
+ return { ...v, remotes: v.remotes.filter((r) => r !== copy) };
3172
+ }).filter((v) => v.remotes.length > 0);
3173
+ for (const { tag, meta } of moved) {
3174
+ const scoped = rowAt(versions, tag, "scope");
3175
+ if (!scoped) versions.push({ tag, action: "scope", host: false, remotes: [meta] });
3176
+ else versions[versions.indexOf(scoped)] = { ...scoped, remotes: [...scoped.remotes, meta] };
3177
+ }
3178
+ return { ...external, versions: versions.sort(byTag(compare)) };
3053
3179
  }
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);
3180
+ function recordFromMap(external, remote) {
3181
+ const opened = external.versions.find(
3182
+ (v) => v.action !== "skip" && v.remotes.some((r) => r.name === remote)
3183
+ );
3184
+ const meta = { ...opened.remotes.find((r) => r.name === remote), cached: false };
3185
+ const joined = rowAt(external.versions, opened.tag, "skip");
3186
+ const into = joined ? { ...joined, remotes: [...joined.remotes] } : { ...opened, action: "skip", remotes: [] };
3187
+ addRemoteToVersion(into, meta);
3188
+ const versions = external.versions.flatMap((v) => {
3189
+ if (v === joined) return [into];
3190
+ if (v !== opened) return [v];
3191
+ const left = { ...v, remotes: v.remotes.filter((r) => r.name !== remote) };
3192
+ return [...left.remotes.length > 0 ? [left] : [], ...joined ? [] : [into]];
3193
+ });
3194
+ return { ...external, versions };
3195
+ }
3196
+ function withoutPoolResults(external) {
3197
+ const { poolName: _poolName, poolWinner: _poolWinner, ...rest } = external;
3198
+ return {
3199
+ ...rest,
3200
+ versions: external.versions.map((v) => ({
3201
+ ...v,
3202
+ remotes: v.remotes.map(({ servedBy: _servedBy, poolCause: _poolCause, ...meta }) => meta)
3203
+ }))
3064
3204
  };
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
3205
  }
3206
+ function renamedRecords(scope, pools) {
3207
+ const renamed = [];
3208
+ for (const [poolName, members] of pools)
3209
+ for (const { name } of members)
3210
+ if (scope[name].poolName !== poolName) renamed.push([name, { ...scope[name], poolName }]);
3211
+ return renamed;
3212
+ }
3213
+
3214
+ // src/lib/core/1.domain/pooling/plan.ts
3215
+ function planElection(scope, poolable) {
3216
+ if (!poolable || !Object.values(scope).some((external) => external.dirty) || !scopeHasPoolState(scope))
3217
+ return { pools: /* @__PURE__ */ new Map(), stripped: /* @__PURE__ */ new Map(), labelledAlone: [] };
3218
+ const { pools, labelledAlone } = buildPools(scope);
3219
+ const pooled = new Set([...pools.values()].flatMap((members) => members.map((m) => m.name)));
3220
+ const stripped = /* @__PURE__ */ new Map();
3221
+ for (const [name, external] of Object.entries(scope))
3222
+ if (!pooled.has(name) && hasPoolResults(external))
3223
+ stripped.set(name, { ...withoutPoolResults(external), dirty: true });
3224
+ return { pools, stripped, labelledAlone };
3225
+ }
3226
+
3227
+ // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
3083
3228
  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;
3089
- const sharedExternals = ports.sharedExternalsRepo.getFromScope(scope);
3229
+ const { compare } = ports.versionCheck;
3230
+ return () => {
3231
+ const repo = ports.sharedExternalsRepo;
3232
+ for (const scope of repo.getScopes()) {
3233
+ const sharedExternals = repo.getFromScope(scope);
3090
3234
  try {
3091
- for (const [poolName, members] of buildPools(
3092
- sharedExternals,
3093
- useAutoExternalPooling,
3094
- config.log
3095
- )) {
3096
- if (touchedInScope && !members.some((m) => touchedInScope.has(m.name))) continue;
3097
- poolFamily(poolName, members, scope);
3098
- }
3235
+ const plan = planElection(sharedExternals, repo.scopeType(scope) !== "strict");
3236
+ logPlan(plan, sharedExternals, scope);
3237
+ for (const [poolName, members] of plan.pools)
3238
+ for (const [name, record] of poolRecords(poolName, members, scope))
3239
+ repo.addOrUpdate(name, record, scope);
3240
+ for (const [name, external] of plan.stripped) repo.addOrUpdate(name, external, scope);
3099
3241
  } catch (error) {
3100
3242
  if (error instanceof NFError) return Promise.reject(error);
3101
3243
  config.log.error(3, `[${scope}] failed to pool shared externals.`, {
@@ -3109,215 +3251,439 @@ function createPoolSharedExternals(config, ports) {
3109
3251
  }
3110
3252
  return Promise.resolve();
3111
3253
  };
3112
- function poolFamily(poolName, members, scope) {
3113
- if (members.length < 2) return;
3114
- const allRemotes = remotesInPool(members);
3115
- if (allRemotes.length < 2) return;
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
- };
3124
- config.log.debug(
3125
- 3,
3126
- `[${scope}][pool:${poolName}] ${members.length} members across ${allRemotes.length} remotes, incompatible={${[...islanded.keys()].join(", ") || "\u2205"}}
3127
- ` + members.map((m) => ` - ${m.name}`).join("\n")
3128
- );
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());
3254
+ function logPlan(plan, sharedExternals, scope) {
3255
+ if (plan.pools.size > 0) {
3256
+ const dirty = Object.values(sharedExternals).filter((external) => external.dirty).length;
3257
+ config.log.debug(
3258
+ 3,
3259
+ `[${scope}] re-electing ${plan.pools.size} pool(s): ${dirty} dirty external(s).`
3260
+ );
3142
3261
  }
3143
- const incompatible = [...islanded].filter(([, cause]) => cause.kind === "incompatible").map(([remote]) => remote);
3144
- if (config.strict.strictExternalCompatibility && incompatible.length > 0) {
3262
+ if (plan.stripped.size > 0)
3263
+ config.log.debug(
3264
+ 3,
3265
+ `[${scope}] ${plan.stripped.size} external(s) left every pool; cleared their pool state for re-election.`
3266
+ );
3267
+ for (const name of plan.labelledAlone)
3268
+ config.log.debug(
3269
+ 3,
3270
+ `[${scope}] '${name}' has a 'pool' label that joins no other external; likely a typo or a missing sibling.`
3271
+ );
3272
+ }
3273
+ function poolRecords(poolName, members, scope) {
3274
+ const election = elect({
3275
+ members,
3276
+ acceptsTag: createAcceptsTag(ports.versionCheck.isCompatible, compare),
3277
+ compare,
3278
+ previousWinner: previousWinner(members),
3279
+ latestFirst: config.profile.latestSharedExternal
3280
+ });
3281
+ const placed = { ...election, poolName };
3282
+ report(poolName, members, placed, scope);
3283
+ return members.map((m) => [
3284
+ m.name,
3285
+ { ...memberRecord(m, placed, compare), poolWinner: placed.winner }
3286
+ ]);
3287
+ }
3288
+ function report(poolName, members, placed, scope) {
3289
+ const { misses } = placed;
3290
+ const rejecting = [...misses].filter(([, miss]) => miss?.cause === "incompatible" && miss.strict).map(([remote]) => remote);
3291
+ if (config.strict.strictExternalCompatibility && rejecting.length > 0) {
3145
3292
  config.log.error(
3146
3293
  3,
3147
- `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${incompatible.join(", ")}}.`
3294
+ `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${rejecting.join(", ")}}.`
3148
3295
  );
3149
3296
  throw new NFError(`Could not pool '${poolName}' in scope ${scope}.`);
3150
3297
  }
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
- }
3155
- for (const member of members) {
3156
- const rebuilt = rebuildMember(member, islanded, served, basis.get(member.name));
3157
- warnIfScopedOnly(poolName, member, rebuilt, islanded, scope);
3158
- ports.sharedExternalsRepo.addOrUpdate(member.name, rebuilt, scope);
3159
- }
3160
- }
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);
3298
+ const subpoolSizes = /* @__PURE__ */ new Map();
3299
+ for (const placement of placed.placements.values())
3300
+ if (placement.kind === "subpool")
3301
+ subpoolSizes.set(placement.owner, (subpoolSizes.get(placement.owner) ?? 0) + 1);
3302
+ let shipped;
3303
+ for (const [remote, miss] of misses) {
3304
+ if (miss === void 0) {
3305
+ config.log.warn(
3306
+ 3,
3307
+ `[${scope}][pool:${poolName}] '${remote}' keeps subpool '${remote}': the elected build would serve it, but ${subpoolSizes.get(remote) - 1} other remote(s) in it need its build.`
3308
+ );
3309
+ continue;
3206
3310
  }
3207
- if (!scoped) return serving;
3311
+ shipped ??= copiesByRemote(members);
3312
+ const counts = {
3313
+ imports: specifierCount(shipped.get(remote)),
3314
+ subpoolSize: subpoolSizes.get(remote) ?? 0
3315
+ };
3316
+ config.log.warn(
3317
+ 3,
3318
+ `[${scope}][pool:${poolName}] ${missWarning(remote, miss, placed, counts)}`
3319
+ );
3208
3320
  }
3209
3321
  }
3210
- function warnIfScopedOnly(poolName, member, rebuilt, islanded, scope) {
3211
- if (rebuilt.versions.some((v) => v.action === "share")) return;
3212
- const consumers = new Set(
3213
- rebuilt.versions.filter((v) => v.action === "scope").flatMap((v) => v.remotes.map((r) => r.name))
3214
- ).size;
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;
3218
- config.log.warn(
3219
- 3,
3220
- `[${scope}][pool:${poolName}] '${member.name}' is scoped-only \u2014 no coherent shared build provides it; ${consumers} remotes download their own copy.`
3221
- );
3322
+ }
3323
+ function missWarning(remote, miss, placed, counts) {
3324
+ const { imports, subpoolSize } = counts;
3325
+ const placement = placed.placements.get(remote);
3326
+ const where = placement.kind === "subpool" && placement.owner !== remote ? `It runs in subpool '${placement.owner}' for all ${imports} of its entrypoints.` : subpoolSize > 1 ? `Its build serves subpool '${remote}': its ${imports} entrypoints and ${subpoolSize - 1} other remote(s).` : `All ${imports} of its entrypoints are scoped for it.`;
3327
+ if (miss.cause === "incompatible")
3328
+ return `'${remote}' is islanded: its range rejects '${miss.member}@${miss.tag}' of the elected build '${placed.winner}'. ${where}`;
3329
+ if (miss.unshipped)
3330
+ return `'${remote}' serves its own family: no build shipped its entrypoints together at the elected versions (gap '${miss.gap}', closest '${placed.winner}'). ${where}`;
3331
+ return `'${remote}' serves its own family: no elected build offers every entrypoint it imports at a version it accepts (gap '${miss.gap}', closest '${placed.winner}'). ${where}`;
3332
+ }
3333
+
3334
+ // src/lib/core/1.domain/externals/serving.ts
3335
+ var byServingRank = (a, b) => Number(b.host) - Number(a.host) || a.downloads - b.downloads || a.fallbacks - b.fallbacks || Number(b.rootBasis) - Number(a.rootBasis) || compareStrings(a.remote, b.remote);
3336
+ var packagesOf = (scope) => groupByPackage(Object.keys(scope), (name) => name);
3337
+ var isPooled = (externals) => externals.some(([, e]) => e.poolName !== void 0);
3338
+ function servingBuildOf(externals, accepts) {
3339
+ if (externals.length === 0) return void 0;
3340
+ const pkg = packageOf(externals[0][0]);
3341
+ const rootRows = shareRows(externals.find(([name]) => name === pkg)?.[1]);
3342
+ const rows = rootRows.length > 0 ? rootRows : externals.flatMap(([, e]) => shareRows(e));
3343
+ if (isPooled(externals)) return pooledBuild(externals, rows);
3344
+ const consumers = consumersOf(externals);
3345
+ const builds = buildsOf(rows, rootRows, consumers);
3346
+ const hosts = builds.filter((b) => b.host);
3347
+ const field = hosts.length > 0 ? hosts : builds;
3348
+ if (field.length <= 1) return field[0] && { remote: field[0].remote, tag: field[0].tag };
3349
+ return cheapestBuild(field, costTable(consumers, accepts));
3350
+ }
3351
+ function cheapestBuild(field, costOf) {
3352
+ let best;
3353
+ for (const b of field) {
3354
+ const { downloads, fallbacks } = costOf(b);
3355
+ const candidate = {
3356
+ remote: b.remote,
3357
+ tag: b.tag,
3358
+ host: b.host,
3359
+ rootBasis: b.rootBasis,
3360
+ downloads,
3361
+ fallbacks
3362
+ };
3363
+ if (!best || byServingRank(candidate, best) < 0) best = candidate;
3222
3364
  }
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);
3365
+ const { remote, tag } = best;
3366
+ return { remote, tag };
3367
+ }
3368
+ function shareServingCopies(external, serving) {
3369
+ for (const row of external.versions) {
3370
+ const copy = row.remotes.find((c) => c.name === serving.remote);
3371
+ if (row.action !== "skip" || !copy || rowAt(external.versions, row.tag, "share")) continue;
3372
+ row.action = "share";
3373
+ if (row.remotes[0] !== copy) row.host = false;
3374
+ row.remotes = [copy, ...row.remotes.filter((c) => c !== copy)];
3375
+ }
3376
+ }
3377
+ function followServingBuild(external, serving, accepts) {
3378
+ let objector;
3379
+ const rebuilt = external.versions.flatMap((version) => {
3380
+ if (version.action !== "share" || version.tag === serving.tag || version.remotes[0].name === serving.remote)
3381
+ return [version];
3382
+ const rows = redirectRow(version, serving.tag, accepts);
3383
+ const scoped = rows.find((row) => row.action === "scope");
3384
+ if (scoped && !objector) objector = { copy: scoped.remotes[0], tag: scoped.tag };
3385
+ return rows;
3386
+ });
3387
+ external.versions = mergeRows(rebuilt);
3388
+ return { external, objector };
3389
+ }
3390
+ var sameTagOnly = (offered, ownTag) => offered === ownTag;
3391
+ var servedScopes = (scope, strict) => strict ? strictTagScopes(scope) : [scope];
3392
+ function strictTagScopes(scope) {
3393
+ const tagScopes = [];
3394
+ for (const [pkg, names] of packagesOf(scope)) {
3395
+ const byTag2 = /* @__PURE__ */ new Map();
3396
+ for (const name of names)
3397
+ for (const row of scope[name].versions) {
3398
+ let tagScope = byTag2.get(row.tag);
3399
+ if (!tagScope) byTag2.set(row.tag, tagScope = {});
3400
+ const external = tagScope[name] ??= { ...scope[name], versions: [] };
3401
+ external.versions.push({ ...row, remotes: [...row.remotes] });
3236
3402
  }
3403
+ for (const [tag, tagScope] of byTag2) {
3404
+ const lead = [tagScope[pkg], ...Object.values(tagScope)].map((external) => external && rowAt(external.versions, tag, "share")).find((row) => row !== void 0);
3405
+ if (lead) {
3406
+ const serving = { remote: lead.remotes[0].name, tag };
3407
+ for (const external of Object.values(tagScope)) external.servingBuild = serving.remote;
3408
+ scopeFallbacks(Object.entries(tagScope), serving, sameTagOnly);
3409
+ }
3410
+ tagScopes.push(tagScope);
3237
3411
  }
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" }));
3412
+ }
3413
+ return tagScopes;
3414
+ }
3415
+ function shareRows(external) {
3416
+ return external?.versions.filter((v) => v.action === "share") ?? [];
3417
+ }
3418
+ function buildsOf(rows, rootRows, consumers) {
3419
+ const candidates = /* @__PURE__ */ new Map();
3420
+ for (const row of rows)
3421
+ for (const copy of row.remotes) {
3422
+ const key = `${row.tag}|${copy.name}`;
3423
+ if (copy.servedBy || candidates.has(key) || shipsTwoTags(consumers.get(copy.name))) continue;
3424
+ const basis = copy === row.remotes[0];
3425
+ candidates.set(key, {
3426
+ remote: copy.name,
3427
+ tag: row.tag,
3428
+ host: basis && row.host,
3429
+ rootBasis: basis && rootRows.includes(row)
3430
+ });
3247
3431
  }
3248
- const shareEntries = clean.filter((e) => e.action === "share").sort((a, b) => Number(b.remote === basis) - Number(a.remote === basis));
3249
- const shareVersion = shareEntries.length > 0 ? [
3250
- {
3251
- tag: shareEntries[0].tag,
3252
- host: shareEntries[0].host,
3253
- action: "share",
3254
- remotes: shareEntries.map((e) => e.meta)
3255
- }
3256
- ] : [];
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);
3432
+ return [...candidates.values()];
3433
+ }
3434
+ function pooledBuild(externals, rows) {
3435
+ const winner = externals.find(([, e]) => e.poolWinner !== void 0)?.[1].poolWinner;
3436
+ const shipped = rows.flatMap(
3437
+ (row) => row.remotes.filter((copy) => !copy.servedBy).map((copy) => ({ remote: copy.name, tag: row.tag }))
3438
+ );
3439
+ const own = shipped.find((c) => c.remote === winner);
3440
+ if (own) return own;
3441
+ const width = ({ remote, tag }) => externals.reduce((n, [, e]) => {
3442
+ const copy = shareRows(e).find((row) => row.tag === tag)?.remotes.find((c) => c.name === remote);
3443
+ return n + Object.keys(copy?.entries ?? {}).length;
3444
+ }, 0);
3445
+ return shipped.map((c) => ({ c, width: width(c) })).sort((a, b) => b.width - a.width || compareStrings(a.c.remote, b.c.remote))[0]?.c;
3446
+ }
3447
+ function consumersOf(externals) {
3448
+ const consumers = /* @__PURE__ */ new Map();
3449
+ for (const [, external] of externals)
3450
+ for (const row of external.versions)
3451
+ for (const copy of row.remotes) {
3452
+ const placed = consumers.get(copy.name);
3453
+ if (placed) placed.push({ row, copy });
3454
+ else consumers.set(copy.name, [{ row, copy }]);
3268
3455
  }
3269
- return [...versions.values()];
3270
- };
3456
+ return consumers;
3457
+ }
3458
+ function costTable(consumers, accepts) {
3459
+ const scanned = /* @__PURE__ */ new Map();
3460
+ const keyed = /* @__PURE__ */ new Map();
3461
+ const tollFor = (build, surface) => {
3462
+ let atTag = scanned.get(build.tag);
3463
+ if (!atTag) scanned.set(build.tag, atTag = []);
3464
+ const known = atTag.find((t) => sameSurface(t.surface, surface));
3465
+ if (known) return known.toll;
3466
+ if (atTag.length < SCANNED_SURFACES_PER_TAG) {
3467
+ const toll2 = tollOf(consumers, surface, build.tag, accepts);
3468
+ atTag.push({ surface, toll: toll2 });
3469
+ return toll2;
3470
+ }
3471
+ const key = [build.tag, ...[...surface].map(([s, tag]) => `${s}@${tag}`).sort()].join("\n");
3472
+ let toll = keyed.get(key);
3473
+ if (!toll) keyed.set(key, toll = tollOf(consumers, surface, build.tag, accepts));
3474
+ return toll;
3475
+ };
3476
+ return (build) => {
3477
+ const surface = surfaceOf(consumers, build);
3478
+ const toll = tollFor(build, surface);
3479
+ const others = (remotes) => remotes.size - Number(remotes.has(build.remote));
3271
3480
  return {
3272
- dirty: false,
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))
3481
+ downloads: others(toll.downloads) + Number(!allCached(consumers.get(build.remote) ?? [])),
3482
+ fallbacks: others(toll.fallbacks)
3281
3483
  };
3282
- }
3484
+ };
3485
+ }
3486
+ function tollOf(consumers, surface, tag, accepts) {
3487
+ const toll = { fallbacks: /* @__PURE__ */ new Set(), downloads: /* @__PURE__ */ new Set() };
3488
+ for (const [remote, placed] of consumers) {
3489
+ const fallback = fallsBack(placed, surface, tag, accepts);
3490
+ if (fallback) toll.fallbacks.add(remote);
3491
+ if (!allCached(placed) && (fallback || runsOffMap(placed, tag, accepts)))
3492
+ toll.downloads.add(remote);
3493
+ }
3494
+ return toll;
3495
+ }
3496
+ var allCached = (placed) => placed.every(({ copy }) => copy.cached);
3497
+ function fallbacksOf(externals, serving, accepts) {
3498
+ const consumers = consumersOf(externals);
3499
+ const surface = serving ? surfaceOf(consumers, serving) : /* @__PURE__ */ new Map();
3500
+ return [...consumers].filter(
3501
+ ([remote, placed]) => remote !== serving?.remote && fallsBack(placed, surface, serving?.tag, accepts)
3502
+ ).map(([remote]) => remote);
3283
3503
  }
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.`;
3504
+ function scopeFallbacks(externals, serving, accepts) {
3505
+ const remotes = fallbacksOf(externals, serving, accepts);
3506
+ for (const [, external] of externals)
3507
+ external.versions = scopeRemotes(external.versions, remotes);
3508
+ }
3509
+ function strictObjectorOf(externals, serving, accepts) {
3510
+ const consumers = consumersOf(externals);
3511
+ const surface = surfaceOf(consumers, serving);
3512
+ for (const [name, external] of externals)
3513
+ for (const row of external.versions)
3514
+ for (const copy of row.remotes)
3515
+ if (copy.name !== serving.remote && rejectsServingTag({ row, copy }, surface, serving.tag, accepts) && !splitsPackage(consumers.get(copy.name), surface, serving.tag, accepts))
3516
+ return { external: name, copy, tag: row.tag };
3517
+ return void 0;
3518
+ }
3519
+ var SCANNED_SURFACES_PER_TAG = 8;
3520
+ function sameSurface(a, b) {
3521
+ if (a.size !== b.size) return false;
3522
+ for (const [specifier, tag] of a) if (b.get(specifier) !== tag) return false;
3523
+ return true;
3524
+ }
3525
+ function surfaceOf(consumers, serving) {
3526
+ const surface = /* @__PURE__ */ new Map();
3527
+ for (const p of consumers.get(serving.remote) ?? [])
3528
+ if (p.row.tag === serving.tag && onMap(p) || leadsShareRow(p)) {
3529
+ for (const s in p.copy.entries) if (!surface.has(s)) surface.set(s, p.row.tag);
3293
3530
  }
3294
- }
3531
+ return surface;
3532
+ }
3533
+ var leadsShareRow = ({ row, copy }) => row.action === "share" && row.remotes[0] === copy;
3534
+ var onMap = ({ row }) => row.action !== "scope";
3535
+ function splitsPackage(placed, surface, tag, accepts) {
3536
+ const uncovered = (p) => Object.keys(p.copy.entries).some((s) => {
3537
+ const servedAt = surface.get(s);
3538
+ return servedAt === void 0 || servedAt !== tag && rejectsTag(p, servedAt, accepts);
3539
+ });
3540
+ return placed.some(onMap) && (shipsTwoTags(placed) || placed.some((p) => !onMap(p) || uncovered(p)));
3541
+ }
3542
+ function loadSplitsPackage(copies, claims, accepts) {
3543
+ const root = claims.find(([specifier]) => specifier === packageOf(specifier))?.[1];
3544
+ const servingTag = root?.tag ?? claims[0][1].tag;
3545
+ const surface = new Map(
3546
+ claims.map(([specifier, { tag }]) => [specifier, root ? tag : servingTag])
3547
+ );
3548
+ const placed = copies.map(({ tag, copy }) => ({
3549
+ row: { tag, action: "share", host: false, remotes: [copy] },
3550
+ copy
3551
+ }));
3552
+ return splitsPackage(placed, surface, servingTag, accepts);
3553
+ }
3554
+ function fallsBack(placed, surface, tag, accepts) {
3555
+ return splitsPackage(placed, surface, tag, accepts) || tag !== void 0 && placed.some((p) => rejectsServingTag(p, surface, tag, accepts));
3556
+ }
3557
+ function rejectsServingTag(p, surface, tag, accepts) {
3558
+ return onMap(p) && rejectsTag(p, tag, accepts) && Object.keys(p.copy.entries).some((s) => surface.get(s) === tag);
3559
+ }
3560
+ var rejectsTag = ({ row, copy }, tag, accepts) => copy.strictVersion && !accepts(tag, row.tag, copy.requiredVersion);
3561
+ var shipsTwoTags = (placed) => !placed.some((p) => p.row.host && p.row.remotes[0] === p.copy) && placed.some((p) => p.row.tag !== placed[0].row.tag);
3562
+ function runsOffMap(placed, tag, accepts) {
3563
+ return placed.some((p) => !onMap(p) || rejectsTag(p, tag, accepts));
3295
3564
  }
3296
3565
 
3297
- // src/lib/core/2.app/steps/pooling/mark-pools-for-reelection.ts
3298
- function createMarkPoolsForReelection(config, ports) {
3566
+ // src/lib/core/2.app/steps/decide-serving-builds.ts
3567
+ function createDecideServingBuilds(config, ports) {
3299
3568
  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
- }
3569
+ const accepts = createAcceptsTag(ports.versionCheck.isCompatible, ports.versionCheck.compare);
3570
+ const repo = ports.sharedExternalsRepo;
3571
+ const written = new Set(repo.uncommittedScopes());
3572
+ const needsDeciding = (shareScope) => written.has(shareScope) || repo.isUndecided(shareScope);
3573
+ try {
3574
+ for (const shareScope of repo.getScopes()) {
3575
+ if (repo.scopeType(shareScope) === "strict" || !needsDeciding(shareScope)) continue;
3576
+ const sharedExternals = repo.getFromScope(shareScope);
3577
+ for (const [pkg, names] of packagesOf(sharedExternals)) {
3578
+ const externals = names.map((name) => [name, sharedExternals[name]]);
3579
+ decidePackage(shareScope, pkg, externals, accepts);
3580
+ }
3581
+ repo.markDecided(shareScope);
3312
3582
  }
3313
- if (spread > 0)
3314
- config.log.debug(3, `[${scope}] ${spread} pool member(s) marked dirty for re-election.`);
3583
+ return Promise.resolve();
3584
+ } catch (e) {
3585
+ return Promise.reject(e);
3315
3586
  }
3316
- return Promise.resolve();
3317
3587
  };
3588
+ function decidePackage(shareScope, pkg, externals, accepts) {
3589
+ const serving = servingBuildOf(externals, accepts);
3590
+ const before = externals.map(([, external]) => shapeOf(external));
3591
+ const pooled = isPooled(externals);
3592
+ const toFollow = pooled ? void 0 : serving;
3593
+ if (!pooled) fallBack(pkg, externals, accepts, serving);
3594
+ externals.forEach(([name, external], i) => {
3595
+ const decided = withServingBuild(
3596
+ toFollow ? follow(pkg, name, external, toFollow, accepts) : external,
3597
+ serving?.remote
3598
+ );
3599
+ if (!sameShape(before[i], decided))
3600
+ ports.sharedExternalsRepo.addOrUpdate(name, decided, shareScope);
3601
+ });
3602
+ }
3603
+ function follow(pkg, name, external, serving, accepts) {
3604
+ const { external: followed, objector } = followServingBuild(external, serving, accepts);
3605
+ if (objector && config.strict.strictExternalCompatibility)
3606
+ rejectIncompatible(config, objector.copy, `${name}@${objector.tag}`, `${pkg}@${serving.tag}`);
3607
+ return followed;
3608
+ }
3609
+ function fallBack(pkg, externals, accepts, serving) {
3610
+ if (serving) {
3611
+ for (const [, external] of externals) shareServingCopies(external, serving);
3612
+ const objector = config.strict.strictExternalCompatibility && strictObjectorOf(externals, serving, accepts);
3613
+ if (objector)
3614
+ rejectIncompatible(
3615
+ config,
3616
+ objector.copy,
3617
+ `${objector.external}@${objector.tag}`,
3618
+ `${pkg}@${serving.tag}`
3619
+ );
3620
+ }
3621
+ scopeFallbacks(externals, serving, accepts);
3622
+ }
3623
+ }
3624
+ function withServingBuild(external, servingBuild) {
3625
+ if (external.servingBuild === servingBuild) return external;
3626
+ const { servingBuild: _, ...rest } = external;
3627
+ return servingBuild ? { ...rest, servingBuild } : rest;
3628
+ }
3629
+ var shapeOf = (external) => ({
3630
+ servingBuild: external.servingBuild,
3631
+ rows: external.versions.map(
3632
+ ({ tag, action, host, remotes }) => ({ tag, action, host, remotes: [...remotes] })
3633
+ )
3634
+ });
3635
+ var sameRow = (was, row) => Object.keys(was).every(
3636
+ (key) => key === "remotes" ? was.remotes.length === row.remotes.length && row.remotes.every((copy, j) => copy === was.remotes[j]) : was[key] === row[key]
3637
+ );
3638
+ var sameShape = (shape, external) => shape.servingBuild === external.servingBuild && shape.rows.length === external.versions.length && external.versions.every((row, i) => sameRow(shape.rows[i], row));
3639
+
3640
+ // src/lib/core/1.domain/externals/claims.ts
3641
+ function claimTable(externals, publishes) {
3642
+ const table = { claims: /* @__PURE__ */ new Map(), duplicates: /* @__PURE__ */ new Set(), shareClaims: /* @__PURE__ */ new Map() };
3643
+ const ordered = [...externals];
3644
+ const claim = (into, external, version, accepts = publishes) => forEachVersionEntry(version, accepts, (specifier, copy) => {
3645
+ if (!into.has(specifier)) into.set(specifier, { external, tag: version.tag, copy });
3646
+ });
3647
+ const seedServingBuild = (name, external) => {
3648
+ const isServing = (copy) => copy.name === external.servingBuild && (!publishes || publishes(copy));
3649
+ for (const version of external.versions) {
3650
+ if (version.action !== "share") continue;
3651
+ claim(table.shareClaims, name, version, isServing);
3652
+ claim(table.claims, name, version, isServing);
3653
+ }
3654
+ };
3655
+ const claimShareRow = (name, external, version) => {
3656
+ const prior = table.claims.get(name);
3657
+ const duplicate = prior !== void 0 && prior.tag !== version.tag;
3658
+ if (duplicate && !(version.host && version.remotes[0].name === external.servingBuild))
3659
+ table.duplicates.add(version);
3660
+ if (external.servingBuild) return;
3661
+ claim(table.shareClaims, name, version);
3662
+ if (!duplicate && (!prior || prior.external === name)) claim(table.claims, name, version);
3663
+ };
3664
+ for (const [name, external] of ordered)
3665
+ if (external.servingBuild) seedServingBuild(name, external);
3666
+ for (const [name, external] of ordered)
3667
+ for (const version of external.versions)
3668
+ if (version.action === "share") claimShareRow(name, external, version);
3669
+ return table;
3670
+ }
3671
+ function committedClaims(externals, { shareScope, without }) {
3672
+ const publishes = (copy) => copy.name !== without && (shareScope !== GLOBAL_SCOPE || copy.cached);
3673
+ return claimTable(externals, publishes).claims;
3674
+ }
3675
+ var claimsByPackage = (claims) => groupByPackage(claims, ([specifier]) => specifier);
3676
+ function versionClaims(version, servingBuild) {
3677
+ const claims = /* @__PURE__ */ new Map();
3678
+ const claim = (specifier, copy) => {
3679
+ if (!claims.has(specifier)) claims.set(specifier, copy);
3680
+ };
3681
+ const accepts = servingBuild ? (copy) => copy.name === servingBuild : void 0;
3682
+ forEachVersionEntry(version, accepts, claim);
3683
+ return claims;
3318
3684
  }
3319
3685
 
3320
- // node_modules/.pnpm/@softarc+native-federation@4.5.0_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
3686
+ // node_modules/.pnpm/@softarc+native-federation@4.8.0/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
3321
3687
  var CHUNK_PREFIX = "@nf-internal";
3322
3688
  function toChunkImport(fileName) {
3323
3689
  if (fileName.startsWith("./")) {
@@ -3334,8 +3700,8 @@ function createGenerateImportMap(config, ports) {
3334
3700
  const chunkBundles = {};
3335
3701
  try {
3336
3702
  addScopedExternals(importMap, chunkBundles);
3337
- addshareScopeExternals(importMap, chunkBundles);
3338
- addGlobalSharedExternals(importMap, chunkBundles);
3703
+ addNamedShareScopes(importMap, chunkBundles);
3704
+ addGlobalShareScope(importMap, chunkBundles);
3339
3705
  addRemoteInfos(importMap, chunkBundles);
3340
3706
  addChunkImports(importMap, chunkBundles);
3341
3707
  return Promise.resolve(importMap);
@@ -3369,47 +3735,61 @@ function createGenerateImportMap(config, ports) {
3369
3735
  }
3370
3736
  return modules;
3371
3737
  }
3372
- function addshareScopeExternals(importMap, chunkBundles) {
3738
+ function addNamedShareScopes(importMap, chunkBundles) {
3373
3739
  const shareScopes = ports.sharedExternalsRepo.getScopes({ includeGlobal: false });
3374
3740
  for (const shareScope of shareScopes) {
3375
- processshareScope(importMap, shareScope, chunkBundles);
3741
+ const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
3742
+ const cachedBefore = /* @__PURE__ */ new Map();
3743
+ for (const [externalName, external] of Object.entries(sharedExternals))
3744
+ cachedBefore.set(externalName, cachedFingerprint(external));
3745
+ const strict = ports.sharedExternalsRepo.scopeType(shareScope) === "strict";
3746
+ for (const served of servedScopes(sharedExternals, strict))
3747
+ addShareScope(importMap, shareScope, served, chunkBundles);
3748
+ for (const [externalName, external] of Object.entries(sharedExternals)) {
3749
+ if (cachedFingerprint(external) !== cachedBefore.get(externalName)) {
3750
+ ports.sharedExternalsRepo.addOrUpdate(externalName, external, shareScope);
3751
+ }
3752
+ }
3376
3753
  }
3377
3754
  return importMap;
3378
3755
  }
3379
- function processshareScope(importMap, shareScope, chunkBundles) {
3380
- const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
3756
+ function addShareScope(importMap, shareScope, sharedExternals, chunkBundles) {
3381
3757
  const index = buildServedIndex(sharedExternals);
3758
+ const claims = shareRowMappings(sharedExternals, shareScope);
3759
+ const claimedElsewhere = (externalName, specifier) => {
3760
+ const claim = claims.get(specifier);
3761
+ return claim?.external === externalName ? void 0 : claim;
3762
+ };
3382
3763
  for (const [externalName, external] of Object.entries(sharedExternals)) {
3383
3764
  let override = void 0;
3384
- const cachedBefore = cachedFingerprint(external);
3385
3765
  for (const version of external.versions) {
3386
3766
  if (version.action === "scope") {
3387
- for (const remote of version.remotes) {
3388
- const remoteScope = getScope2(shareScope, remote.name, externalName);
3389
- for (const [packageName, file] of Object.entries(remote.entries)) {
3390
- const url = join(remoteScope, file);
3391
- addToScope(importMap, remoteScope, { [packageName]: url });
3392
- addIntegrity(importMap, url, remote.name, file);
3393
- }
3394
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3395
- remote.cached = true;
3396
- }
3767
+ addScopeRow(importMap, chunkBundles, shareScope, externalName, version);
3397
3768
  continue;
3398
3769
  }
3399
- version.remotes[0].cached = true;
3400
3770
  let serving = version;
3401
3771
  if (version.action === "skip") {
3402
3772
  if (!override) {
3403
- override = findOverride(external, shareScope, externalName) ?? "NOT_AVAILABLE";
3773
+ override = findOverride(
3774
+ external,
3775
+ shareScope,
3776
+ externalName,
3777
+ (s) => Boolean(claimedElsewhere(externalName, s))
3778
+ ) ?? "NOT_AVAILABLE";
3404
3779
  }
3405
3780
  if (override !== "NOT_AVAILABLE") {
3406
3781
  serving = override;
3407
3782
  version.remotes[0].cached = false;
3408
3783
  }
3409
3784
  }
3410
- const provided = versionEntries(serving);
3785
+ const provided = versionClaims(serving, external.servingBuild);
3411
3786
  const mappings = [];
3412
3787
  for (const [packageName, provider] of provided) {
3788
+ const claim = claimedElsewhere(externalName, packageName);
3789
+ if (claim) {
3790
+ mappings.push({ packageName, ...claim });
3791
+ continue;
3792
+ }
3413
3793
  const file = provider.entries[packageName];
3414
3794
  mappings.push({
3415
3795
  packageName,
@@ -3422,56 +3802,60 @@ function createGenerateImportMap(config, ports) {
3422
3802
  registerBundleChunks(chunkBundles, provider.name, provider.bundle);
3423
3803
  }
3424
3804
  }
3425
- version.remotes.forEach((r) => {
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)) {
3805
+ version.remotes.forEach((consumer) => {
3806
+ const consumerScope = getScope2(shareScope, consumer.name, externalName);
3807
+ if (index && consumer.servedBy) {
3808
+ const files = index.get(consumer.servedBy);
3809
+ const servedScope = getScope2(shareScope, consumer.servedBy, externalName);
3810
+ for (const specifier of Object.keys(consumer.entries)) {
3431
3811
  const source = files?.get(specifier);
3432
3812
  if (!source) continue;
3433
3813
  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);
3814
+ addToScope(importMap, consumerScope, { [specifier]: url });
3815
+ addIntegrity(importMap, url, consumer.servedBy, source.file);
3816
+ registerBundleChunks(chunkBundles, consumer.servedBy, source.bundle);
3437
3817
  source.meta.cached = true;
3438
3818
  }
3439
3819
  return;
3440
3820
  }
3441
3821
  for (const m of mappings) {
3442
- addToScope(importMap, rScope, { [m.packageName]: m.url });
3822
+ addToScope(importMap, consumerScope, { [m.packageName]: m.url });
3443
3823
  }
3444
- for (const [packageName, file] of Object.entries(r.entries)) {
3824
+ for (const packageName of Object.keys(consumer.entries)) {
3445
3825
  if (provided.has(packageName)) continue;
3446
- if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
3447
- warnUncoveredEntrypoint(shareScope, externalName, r.name, packageName);
3448
- continue;
3449
- }
3450
- const url = join(rScope, file);
3451
- addToScope(importMap, rScope, { [packageName]: url });
3452
- addIntegrity(importMap, url, r.name, file);
3453
- r.cached = true;
3826
+ const claim = claimedElsewhere(externalName, packageName);
3827
+ if (claim) addToScope(importMap, consumerScope, { [packageName]: claim.url });
3454
3828
  }
3455
3829
  });
3456
3830
  for (const m of mappings) {
3457
3831
  addIntegrity(importMap, m.url, m.name, m.file);
3458
3832
  }
3459
3833
  }
3460
- if (cachedFingerprint(external) !== cachedBefore) {
3461
- ports.sharedExternalsRepo.addOrUpdate(externalName, external, shareScope);
3834
+ }
3835
+ }
3836
+ function addScopeRow(importMap, chunkBundles, shareScope, externalName, version) {
3837
+ for (const remote of version.remotes) {
3838
+ const remoteScope = getScope2(shareScope, remote.name, externalName);
3839
+ for (const [packageName, file] of Object.entries(remote.entries)) {
3840
+ const url = join(remoteScope, file);
3841
+ addToScope(importMap, remoteScope, { [packageName]: url });
3842
+ addIntegrity(importMap, url, remote.name, file);
3462
3843
  }
3844
+ registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3845
+ remote.cached = true;
3463
3846
  }
3464
3847
  }
3465
3848
  function cachedFingerprint(external) {
3466
3849
  return external.versions.map((v) => v.remotes.map((r) => r.cached ? 1 : 0).join("")).join("|");
3467
3850
  }
3468
- function findOverride(external, shareScope, externalName) {
3851
+ function findOverride(external, shareScope, externalName, servedElsewhere) {
3469
3852
  const sharedVersions = external.versions.filter((v) => v.action === "share");
3470
3853
  const scopedExternalName = `${shareScope}.${externalName}`;
3471
3854
  if (sharedVersions.length > 1) {
3472
3855
  handleMultipleSharedVersions(scopedExternalName);
3473
3856
  }
3474
- if (sharedVersions.length < 1) {
3857
+ const servedByClaims = () => external.versions.filter((v) => v.action === "skip").every((v) => shipsOnly(v, servedElsewhere));
3858
+ if (sharedVersions.length < 1 && !servedByClaims()) {
3475
3859
  if (config.strict.strictImportMap) {
3476
3860
  config.log.error(4, `[${shareScope}][${externalName}] shareScope has no override version.`);
3477
3861
  throw new NFError("Could not create ImportMap.");
@@ -3516,17 +3900,30 @@ function createGenerateImportMap(config, ports) {
3516
3900
  }
3517
3901
  return index;
3518
3902
  }
3519
- function collectServed(into, index, ctx, externalName, version) {
3903
+ function shareRowMappings(externals, shareScope) {
3904
+ const mappings = /* @__PURE__ */ new Map();
3905
+ for (const [specifier, { external, copy }] of claimTable(Object.entries(externals)).shareClaims) {
3906
+ const file = copy.entries[specifier];
3907
+ mappings.set(specifier, {
3908
+ url: join(getScope2(shareScope, copy.name, external), file),
3909
+ name: copy.name,
3910
+ file,
3911
+ external
3912
+ });
3913
+ }
3914
+ return mappings;
3915
+ }
3916
+ function collectServed(into, index, shareScope, externalName, version) {
3520
3917
  for (const consumer of version.remotes) {
3521
3918
  if (!consumer.servedBy) continue;
3522
3919
  const files = index.get(consumer.servedBy);
3523
- const scope = getScope2(ctx, consumer.name, externalName);
3920
+ const scope = getScope2(shareScope, consumer.name, externalName);
3524
3921
  for (const specifier of Object.keys(consumer.entries)) {
3525
3922
  const source = files?.get(specifier);
3526
3923
  if (!source) {
3527
3924
  config.log.warn(
3528
3925
  4,
3529
- `[${ctx}][${externalName}][${consumer.name}] '${consumer.servedBy}' does not serve '${specifier}'.`
3926
+ `[${shareScope}][${externalName}][${consumer.name}] '${consumer.servedBy}' does not serve '${specifier}'.`
3530
3927
  );
3531
3928
  continue;
3532
3929
  }
@@ -3544,9 +3941,11 @@ function createGenerateImportMap(config, ports) {
3544
3941
  source.meta.cached = true;
3545
3942
  }
3546
3943
  }
3547
- function addGlobalSharedExternals(importMap, chunkBundles) {
3944
+ function addGlobalShareScope(importMap, chunkBundles) {
3548
3945
  const sharedExternals = ports.sharedExternalsRepo.getFromScope();
3549
3946
  const index = buildServedIndex(sharedExternals);
3947
+ const { claims, duplicates } = claimTable(Object.entries(sharedExternals));
3948
+ const claimed = claimsByCopy(claims);
3550
3949
  const served = [];
3551
3950
  const cachedBefore = /* @__PURE__ */ new Map();
3552
3951
  for (const [externalName, external] of Object.entries(sharedExternals)) {
@@ -3557,27 +3956,14 @@ function createGenerateImportMap(config, ports) {
3557
3956
  }
3558
3957
  if (version.action === "skip") continue;
3559
3958
  if (version.action === "scope") {
3560
- for (const remote of version.remotes) {
3561
- const remoteScope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
3562
- for (const [packageName, file] of Object.entries(remote.entries)) {
3563
- const url = join(remoteScope, file);
3564
- addToScope(importMap, remoteScope, { [packageName]: url });
3565
- addIntegrity(importMap, url, remote.name, file);
3566
- }
3567
- remote.cached = true;
3568
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3569
- }
3959
+ addScopeRow(importMap, chunkBundles, GLOBAL_SCOPE, externalName, version);
3570
3960
  continue;
3571
3961
  }
3572
- if (importMap.imports[externalName]) {
3962
+ if (duplicates.has(version)) {
3573
3963
  notifyDuplicateGlobalExternal(externalName);
3574
3964
  continue;
3575
3965
  }
3576
- mergeVersionEntries(importMap, chunkBundles, externalName, version);
3577
- }
3578
- for (const version of external.versions) {
3579
- if (version.action !== "skip") continue;
3580
- selfFillUncovered(importMap, chunkBundles, externalName, version.remotes);
3966
+ publishClaims(importMap, chunkBundles, externalName, version, claimed);
3581
3967
  }
3582
3968
  }
3583
3969
  flushServed(importMap, chunkBundles, served);
@@ -3588,54 +3974,30 @@ function createGenerateImportMap(config, ports) {
3588
3974
  }
3589
3975
  return importMap;
3590
3976
  }
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);
3977
+ function claimsByCopy(claims) {
3978
+ const claimed = /* @__PURE__ */ new Map();
3979
+ for (const [specifier, { copy }] of claims) {
3980
+ const specifiers = claimed.get(copy);
3981
+ if (specifiers) specifiers.push(specifier);
3982
+ else claimed.set(copy, [specifier]);
3611
3983
  }
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);
3984
+ return claimed;
3985
+ }
3986
+ function publishClaims(importMap, chunkBundles, externalName, version, claimed) {
3987
+ for (const copy of version.remotes) {
3988
+ const specifiers = claimed.get(copy);
3989
+ if (!specifiers) continue;
3990
+ const scope = getScope2(GLOBAL_SCOPE, copy.name, externalName);
3991
+ registerBundleChunks(chunkBundles, copy.name, copy.bundle);
3992
+ copy.cached = true;
3993
+ for (const specifier of specifiers) {
3994
+ const file = copy.entries[specifier];
3623
3995
  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;
3996
+ addToGlobal(importMap, { [specifier]: url });
3997
+ addIntegrity(importMap, url, copy.name, file);
3628
3998
  }
3629
3999
  }
3630
4000
  }
3631
- function warnUncoveredEntrypoint(scope, externalName, remoteName, packageName) {
3632
- const msg = `[${scope}][${externalName}][${remoteName}] Entrypoint '${packageName}' is not covered by the shared version.`;
3633
- if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
3634
- config.log.error(4, msg);
3635
- throw new NFError("Could not create ImportMap.");
3636
- }
3637
- config.log.warn(4, msg);
3638
- }
3639
4001
  function notifyDuplicateGlobalExternal(externalName) {
3640
4002
  if (config.strict.strictImportMap) {
3641
4003
  config.log.error(4, `[${externalName}] Shared external has multiple shared versions.`);
@@ -3709,15 +4071,30 @@ function createGenerateImportMap(config, ports) {
3709
4071
  });
3710
4072
  }
3711
4073
  }
4074
+ function shipsOnly(version, served) {
4075
+ let only = true;
4076
+ forEachVersionEntry(version, void 0, (specifier) => {
4077
+ only &&= served(specifier);
4078
+ });
4079
+ return only;
4080
+ }
3712
4081
 
3713
4082
  // src/lib/core/2.app/steps/commit-changes.ts
3714
4083
  function createCommitChanges(config, ports) {
3715
- return (importMap, opts = {}) => Promise.resolve(importMap).then((map) => addToBrowser(map, opts)).then(persistRepositoryChanges);
4084
+ return (importMap, { markUndecided = false, ...browserOpts } = {}) => Promise.resolve(importMap).then((map) => addToBrowser(map, browserOpts)).then(() => {
4085
+ if (markUndecided) markUndecidedScopes();
4086
+ persistRepositoryChanges();
4087
+ });
3716
4088
  function addToBrowser(importMap, opts) {
3717
4089
  ports.browser.setImportMapFn(importMap, opts);
3718
4090
  config.log.debug(5, "Added import map to browser.", importMap);
3719
4091
  return importMap;
3720
4092
  }
4093
+ function markUndecidedScopes() {
4094
+ const repo = ports.sharedExternalsRepo;
4095
+ for (const shareScope of repo.uncommittedScopes())
4096
+ if (repo.scopeType(shareScope) !== "strict") repo.markUndecided(shareScope);
4097
+ }
3721
4098
  function persistRepositoryChanges() {
3722
4099
  ports.remoteInfoRepo.commit();
3723
4100
  ports.scopedExternalsRepo.commit();
@@ -3727,6 +4104,29 @@ function createCommitChanges(config, ports) {
3727
4104
  }
3728
4105
  }
3729
4106
 
4107
+ // src/lib/core/2.app/steps/rollback-changes.ts
4108
+ var createStateDump = (config, ports) => (msg) => config.log.debug(0, msg, {
4109
+ remotes: { ...ports.remoteInfoRepo.getAll() },
4110
+ "shared-externals": ports.sharedExternalsRepo.getScopes({ includeGlobal: true }).reduce(
4111
+ (acc, scope) => ({ ...acc, [scope]: ports.sharedExternalsRepo.getFromScope(scope) }),
4112
+ {}
4113
+ ),
4114
+ "scoped-externals": ports.scopedExternalsRepo.getAll()
4115
+ });
4116
+ function createRollbackChanges(config, ports) {
4117
+ const stateDump = createStateDump(config, ports);
4118
+ return (label) => Promise.resolve().then(() => {
4119
+ try {
4120
+ stateDump(`${label} STATE DUMP`);
4121
+ } finally {
4122
+ ports.remoteInfoRepo.rollback();
4123
+ ports.scopedExternalsRepo.rollback();
4124
+ ports.sharedExternalsRepo.rollback();
4125
+ ports.sharedChunksRepo.rollback();
4126
+ }
4127
+ });
4128
+ }
4129
+
3730
4130
  // src/lib/core/2.app/steps/expose-module-loader.ts
3731
4131
  function createExposeModuleLoader(config, ports) {
3732
4132
  const loadRemoteModule = (remoteName, exposedModule) => {
@@ -3762,10 +4162,16 @@ var normalizeRemoteRef = (remote) => {
3762
4162
  function createGetRemoteEntry(config, ports) {
3763
4163
  return async (remoteEntryUrl, remote) => {
3764
4164
  const { name: remoteName, integrity } = normalizeRemoteRef(remote);
3765
- if (!!remoteName && shouldSkipCachedRemote(remoteEntryUrl, remoteName)) {
3766
- config.log.debug(7, `Found remote '${remoteName}' in storage, omitting fetch.`);
4165
+ const knownAtUrl = findCachedRemoteAt(remoteEntryUrl);
4166
+ if (knownAtUrl) {
4167
+ config.log.debug(
4168
+ 7,
4169
+ `Found remote '${knownAtUrl}' at '${remoteEntryUrl}' in storage, omitting fetch.`
4170
+ );
3767
4171
  return Optional.empty();
3768
4172
  }
4173
+ if (!!remoteName && ports.remoteInfoRepo.contains(remoteName))
4174
+ return warnInitialized(remoteName, remoteEntryUrl);
3769
4175
  try {
3770
4176
  const remoteEntry = integrity ? await ports.remoteEntryProvider.provide(remoteEntryUrl, { integrity }) : await ports.remoteEntryProvider.provide(remoteEntryUrl);
3771
4177
  config.log.debug(
@@ -3780,10 +4186,8 @@ function createGetRemoteEntry(config, ports) {
3780
4186
  }
3781
4187
  config.log.warn(7, errorMsg + " Omitting expected name.");
3782
4188
  }
3783
- if (ports.remoteInfoRepo.contains(remoteEntry.name)) {
3784
- remoteEntry.override = true;
3785
- config.log.debug(7, `Overriding existing remote '${remoteName}' with '${remoteEntryUrl}'.`);
3786
- }
4189
+ if (ports.remoteInfoRepo.contains(remoteEntry.name))
4190
+ return warnInitialized(remoteEntry.name, remoteEntryUrl);
3787
4191
  return Optional.of(checkForSSE(remoteEntry));
3788
4192
  } catch (error) {
3789
4193
  config.log.error(
@@ -3796,10 +4200,18 @@ function createGetRemoteEntry(config, ports) {
3796
4200
  );
3797
4201
  }
3798
4202
  };
3799
- function shouldSkipCachedRemote(remoteEntryUrl, remoteName) {
3800
- return ports.remoteInfoRepo.tryGet(remoteName).map(
3801
- (cachedRemoteInfo) => config.profile.overrideCachedRemotes !== "always" || !config.profile.overrideCachedRemotesIfURLMatches && remoteEntryUrl === join(cachedRemoteInfo.scopeUrl, "remoteEntry.json")
3802
- ).orElse(false);
4203
+ function warnInitialized(name, remoteEntryUrl) {
4204
+ const initializedFrom = ports.remoteInfoRepo.tryGet(name).map((info) => join(info.scopeUrl, "remoteEntry.json")).get();
4205
+ config.log.warn(
4206
+ 7,
4207
+ `[${name}] Already initialized from '${initializedFrom}', ignoring '${remoteEntryUrl}'.`
4208
+ );
4209
+ return Optional.empty();
4210
+ }
4211
+ function findCachedRemoteAt(remoteEntryUrl) {
4212
+ return Object.entries(ports.remoteInfoRepo.getAll()).find(
4213
+ ([, info]) => join(info.scopeUrl, "remoteEntry.json") === remoteEntryUrl
4214
+ )?.[0];
3803
4215
  }
3804
4216
  function checkForSSE(entry) {
3805
4217
  if (config.sse) {
@@ -3819,92 +4231,170 @@ function createGetRemoteEntry(config, ports) {
3819
4231
  // src/lib/core/2.app/steps/update-cache.ts
3820
4232
  function createUpdateCache(config, ports) {
3821
4233
  const storeRemoteEntry = createStoreRemoteEntry(config, ports, 8);
3822
- const removeCachedRemoteEntries = createRemoveCachedRemoteEntries(ports);
4234
+ const accepts = createAcceptsTag(
4235
+ (tag, range) => ports.versionCheck.isCompatible(tag, range),
4236
+ (a, b) => ports.versionCheck.compare(a, b)
4237
+ );
3823
4238
  return (remoteEntry) => {
3824
4239
  try {
4240
+ const pending = [];
4241
+ storeRemoteEntry(remoteEntry, (_, external, ctx) => pending.push({ external, ctx }));
4242
+ const decisions = decidePackages(remoteEntry, pending);
3825
4243
  const actions = {};
3826
- if (remoteEntry?.override) removeCachedRemoteEntries(/* @__PURE__ */ new Set([remoteEntry.name]));
3827
- storeRemoteEntry(remoteEntry, (entry, external, ctx) => {
3828
- const { action, provided, sameVersion } = resolveSharedExternal(entry, external, ctx);
3829
- actions[external.packageName] = { action };
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
- }
3840
- }
3841
- });
4244
+ for (const item of pending)
4245
+ actions[item.external.packageName] = applyDecision(remoteEntry, item, decisions.get(item));
3842
4246
  return Promise.resolve({ entry: remoteEntry, actions });
3843
4247
  } catch (error) {
3844
4248
  return Promise.reject(error);
3845
4249
  }
3846
4250
  };
3847
- function resolveSharedExternal(remoteEntry, sharedInfo, {
3848
- tag,
3849
- remote,
3850
- cached: cached2,
3851
- scopeType,
3852
- assertSameVersionCompatibility,
3853
- commit
3854
- }) {
3855
- let action = scopeType === "strict" ? "share" : "skip";
3856
- const sharedVersion = cached2.versions.find((c) => c.action === "share");
3857
- const isCompatible = !sharedVersion || ports.versionCheck.isCompatible(sharedVersion.tag, remote.requiredVersion);
3858
- if (action === "skip" && !isCompatible && remote.strictVersion) {
3859
- action = "scope";
3860
- const errorMsg = `[${sharedInfo.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}] ${sharedInfo.packageName}@${sharedInfo.version} Is not compatible with existing ${sharedInfo.packageName}@${sharedVersion.tag} requiredRange '${sharedVersion.remotes[0]?.requiredVersion}'`;
3861
- if (config.strict.strictExternalCompatibility) {
3862
- config.log.error(8, errorMsg);
3863
- throw new NFError(`Could not process remote '${remoteEntry.name}'`);
3864
- }
3865
- config.log.warn(8, errorMsg);
4251
+ function decidePackages(remoteEntry, pending) {
4252
+ const decisions = /* @__PURE__ */ new Map();
4253
+ const viewOf = loadViewOf(remoteEntry, pending);
4254
+ for (const group of pendingPerPackage(pending)) {
4255
+ const decision = group[0].ctx.scopeType === "strict" ? { action: "share" } : decidePackage(remoteEntry, group, viewOf(group[0].external.shareScope));
4256
+ for (const item of group) decisions.set(item, decision);
3866
4257
  }
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
- }
4258
+ scopeOpenedPackages(decisions);
4259
+ return decisions;
4260
+ }
4261
+ function pendingPerPackage(pending) {
4262
+ const packages = /* @__PURE__ */ new Map();
4263
+ for (const item of pending) {
4264
+ const { shareScope, packageName } = item.external;
4265
+ const key = `${shareScope ?? GLOBAL_SCOPE}\0${packageOf(packageName)}`;
4266
+ const group = packages.get(key);
4267
+ if (group) group.push(item);
4268
+ else packages.set(key, [item]);
4269
+ }
4270
+ return [...packages.values()];
4271
+ }
4272
+ function loadViewOf(remoteEntry, pending) {
4273
+ const views = /* @__PURE__ */ new Map();
4274
+ return (shareScope) => {
4275
+ const scope = shareScope ?? GLOBAL_SCOPE;
4276
+ let view = views.get(scope);
4277
+ if (!view) {
4278
+ const record = ports.sharedExternalsRepo.getFromScope(shareScope);
4279
+ const claims = committedClaims(Object.entries(record), {
4280
+ shareScope: scope,
4281
+ without: remoteEntry.name
4282
+ });
4283
+ const claimsIn = claimsByPackage(claims);
4284
+ let pooled;
4285
+ view = {
4286
+ claims,
4287
+ claimsOf: (pkg) => claimsIn.get(pkg) ?? [],
4288
+ inPool: (pkg) => (pooled ??= pooledPackagesWith(record, copiesIn(pending, scope))).has(pkg)
4289
+ };
4290
+ views.set(scope, view);
3881
4291
  }
4292
+ return view;
4293
+ };
4294
+ }
4295
+ function copiesIn(pending, scope) {
4296
+ return pending.filter(({ external }) => (external.shareScope ?? GLOBAL_SCOPE) === scope).map(({ external, ctx }) => ({ name: external.packageName, pool: ctx.remote.pool }));
4297
+ }
4298
+ function scopeOpenedPackages(decisions) {
4299
+ if (![...decisions.values()].some((d) => d.action === "scope" && d.partlyServed)) return;
4300
+ for (const [item, decision] of decisions)
4301
+ if (decision.action === "share" && item.ctx.scopeType !== "strict")
4302
+ decisions.set(item, { action: "scope", partlyServed: false });
4303
+ }
4304
+ function decidePackage(remoteEntry, group, view) {
4305
+ const pkg = packageOf(group[0].external.packageName);
4306
+ const claims = view.claimsOf(pkg);
4307
+ if (claims.length === 0) return { action: "share" };
4308
+ const specifiers = group.flatMap(({ external }) => Object.keys(external.entries));
4309
+ const builds = specifiers.every((specifier) => view.claims.has(specifier)) ? new Set(specifiers.map((specifier) => view.claims.get(specifier).copy.name)) : void 0;
4310
+ const rejections = group.flatMap((item) => rejectionsOf(item, view.claims));
4311
+ if (rejections.length > 0) {
4312
+ const fromTwoBuilds = builds !== void 0 && builds.size > 1;
4313
+ for (const rejection of refused(group, rejections, fromTwoBuilds, claims, view.inPool(pkg)))
4314
+ reportIncompatible(remoteEntry, rejection);
4315
+ return { action: "scope", partlyServed: false };
3882
4316
  }
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) {
3890
- assertSameVersionCompatibility(matchingVersion);
3891
- addRemoteToVersion(matchingVersion, remote);
3892
- } else {
3893
- if (!sharedVersion) action = "share";
3894
- remote.cached = action !== "skip";
3895
- cached2.versions.push({ tag, action, host: false, remotes: [remote] });
4317
+ if (!builds || builds.size > 1) return { action: "scope", partlyServed: true };
4318
+ return { action: "skip", files: servingFiles(claims, builds) };
4319
+ }
4320
+ function servingFiles(claims, builds) {
4321
+ const files = /* @__PURE__ */ new Map();
4322
+ for (const [specifier, { copy }] of claims)
4323
+ if (builds.has(copy.name)) files.set(specifier, copy);
4324
+ return files;
4325
+ }
4326
+ function rejectionsOf(item, claims) {
4327
+ const { external, ctx } = item;
4328
+ if (!ctx.remote.strictVersion) return [];
4329
+ return Object.keys(external.entries).flatMap((specifier) => {
4330
+ const claim = claims.get(specifier);
4331
+ return claim && !accepts(claim.tag, ctx.tag, ctx.remote.requiredVersion) ? [{ item, specifier, claim }] : [];
4332
+ });
4333
+ }
4334
+ function refused(group, rejections, fromTwoBuilds, claims, pooled) {
4335
+ const copies = group.map(({ ctx }) => ({ tag: ctx.tag, copy: ctx.remote }));
4336
+ const splits = fromTwoBuilds || loadSplitsPackage(copies, claims, accepts);
4337
+ const first = /* @__PURE__ */ new Map();
4338
+ for (const rejection of rejections)
4339
+ if (!first.has(rejection.item) && (pooled || !splits || electionKeeps(rejection)))
4340
+ first.set(rejection.item, rejection);
4341
+ return [...first.values()];
4342
+ }
4343
+ function electionKeeps({ item: { external, ctx }, claim }) {
4344
+ if (claim.external !== external.packageName) return false;
4345
+ const versions = ports.sharedExternalsRepo.tryGet(claim.external, external.shareScope).get()?.versions ?? [];
4346
+ const strict = [
4347
+ { tag: ctx.tag, copy: ctx.remote },
4348
+ ...versions.flatMap(
4349
+ (v) => v.remotes.filter((r) => r.strictVersion).map((copy) => ({ tag: v.tag, copy }))
4350
+ )
4351
+ ];
4352
+ const tags = [ctx.tag, ...versions.map((v) => v.tag)];
4353
+ if (!tags.some((tag) => strict.every((s) => accepts(tag, s.tag, s.copy.requiredVersion))))
4354
+ return true;
4355
+ const host = versions.find((v) => v.host);
4356
+ if (host) return host.tag === claim.tag;
4357
+ if (!config.profile.latestSharedExternal) return false;
4358
+ return tags.every((tag) => ports.versionCheck.compare(claim.tag, tag) >= 0);
4359
+ }
4360
+ function reportIncompatible(remoteEntry, { item: { external, ctx }, specifier, claim }) {
4361
+ const errorMsg = `[${external.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}] ${specifier}@${ctx.tag} Is not compatible with existing ${specifier}@${claim.tag} requiredRange '${ctx.remote.requiredVersion}'`;
4362
+ if (config.strict.strictExternalCompatibility) {
4363
+ config.log.error(8, errorMsg);
4364
+ throw new NFError(`Could not process remote '${remoteEntry.name}'`);
3896
4365
  }
3897
- commit();
3898
- return { action, provided, sameVersion };
4366
+ config.log.warn(8, errorMsg);
4367
+ }
4368
+ function applyDecision(remoteEntry, { external, ctx }, decision) {
4369
+ storeCopy(ctx, decision.action);
4370
+ if (decision.action !== "skip") return { action: decision.action };
4371
+ const covered = Object.keys(external.entries);
4372
+ if (!external.shareScope) return { action: "skip", covered };
4373
+ return {
4374
+ action: "skip",
4375
+ covered,
4376
+ override: overrideFrom(remoteEntry, external, decision.files)
4377
+ };
3899
4378
  }
3900
- function resolveOverrideEntries(remoteEntry, external, provided) {
4379
+ function storeCopy(ctx, action) {
4380
+ const { tag, remote } = ctx;
4381
+ const cached2 = ctx.readRecord();
4382
+ const shared = cached2.versions.find((v) => v.tag === tag && v.action === "share");
4383
+ if (shared && action !== "scope") ctx.assertSameVersionCompatibility(shared);
4384
+ remote.cached = action !== "skip";
4385
+ const row = action === "share" ? shared : cached2.versions.find((v) => v.tag === tag && v.action === action);
4386
+ const loaderServes = action === "share" && ctx.scopeType !== "strict";
4387
+ if (!row) cached2.versions.push({ tag, action, host: false, remotes: [remote] });
4388
+ else if (loaderServes) addRemoteToVersion(row, remote);
4389
+ else row.remotes.push(remote);
4390
+ if (loaderServes) cached2.servingBuild = remote.name;
4391
+ ctx.write();
4392
+ }
4393
+ function overrideFrom(remoteEntry, external, files) {
3901
4394
  return Object.fromEntries(
3902
- Array.from(provided, ([packageName, provider]) => [
3903
- packageName,
3904
- join(
3905
- providerScopeUrl(remoteEntry, external, provider.name),
3906
- provider.entries[packageName]
3907
- )
4395
+ Array.from(files, ([specifier, copy]) => [
4396
+ specifier,
4397
+ join(providerScopeUrl(remoteEntry, external, copy.name), copy.entries[specifier])
3908
4398
  ])
3909
4399
  );
3910
4400
  }
@@ -3919,134 +4409,298 @@ function createUpdateCache(config, ports) {
3919
4409
  }
3920
4410
  }
3921
4411
 
3922
- // src/lib/core/2.app/steps/pooling/pool-dynamic-externals.ts
3923
- function createPoolDynamicExternals(config, ports) {
3924
- return ({ entry, actions }) => {
3925
- const { useAutoExternalPooling } = config.feature;
3926
- const declared = /* @__PURE__ */ new Map();
3927
- for (const external of entry.shared ?? []) {
3928
- const name = external.packageName;
3929
- if (!external.singleton || !actions[name]) continue;
3930
- if (external.shareScope === STRICT_SCOPE) continue;
3931
- const shareScope = external.shareScope ?? GLOBAL_SCOPE;
3932
- let names = declared.get(shareScope);
3933
- if (!names) declared.set(shareScope, names = /* @__PURE__ */ new Set());
3934
- names.add(name);
3935
- }
3936
- if (declared.size === 0) return Promise.resolve({ entry, actions });
3937
- const scope = (name) => {
3938
- actions[name].action = "scope";
3939
- delete actions[name].override;
3940
- };
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.`
4412
+ // src/lib/core/1.domain/pooling/gate.ts
4413
+ function committedView(members, shipped, recordOrder, { shareScope, without }) {
4414
+ const global = /* @__PURE__ */ new Map();
4415
+ const claims = committedClaims(inRecordOrder(members, recordOrder), { shareScope, without });
4416
+ for (const [specifier, { tag, copy }] of claims)
4417
+ global.set(specifier, { tag, remote: copy.name, file: copy.entries[specifier] });
4418
+ const builds = /* @__PURE__ */ new Map();
4419
+ for (const [owner, copies] of shipped)
4420
+ if (owner !== without) builds.set(owner, buildOf(owner, copies));
4421
+ const fileOf = subpoolFilesOf(members);
4422
+ return { builds, global, runsOffMap: runsOffMapOf(members, global, builds, fileOf) };
4423
+ }
4424
+ function subpoolFilesOf(members) {
4425
+ const subpoolOwners = /* @__PURE__ */ new Set();
4426
+ for (const member of members)
4427
+ for (const version of member.external.versions)
4428
+ for (const { servedBy } of version.remotes) if (servedBy) subpoolOwners.add(servedBy);
4429
+ const fileOf = /* @__PURE__ */ new Map();
4430
+ for (const member of members)
4431
+ for (const version of member.external.versions)
4432
+ for (const { name, entries } of version.remotes) {
4433
+ if (!subpoolOwners.has(name)) continue;
4434
+ let files = fileOf.get(name);
4435
+ if (!files) fileOf.set(name, files = /* @__PURE__ */ new Map());
4436
+ for (const specifier in entries)
4437
+ if (!files.has(specifier)) files.set(specifier, entries[specifier]);
4438
+ }
4439
+ return fileOf;
4440
+ }
4441
+ function runsOffMapOf(members, global, builds, fileOf) {
4442
+ const runsOffMap2 = /* @__PURE__ */ new Map();
4443
+ for (const member of members)
4444
+ for (const version of member.external.versions)
4445
+ for (const meta of version.remotes) {
4446
+ const { servedBy } = meta;
4447
+ if (version.action === "share" && !servedBy) continue;
4448
+ let runs = runsOffMap2.get(meta.name);
4449
+ if (!runs) runsOffMap2.set(meta.name, runs = /* @__PURE__ */ new Map());
4450
+ for (const specifier in meta.entries) {
4451
+ if (version.action === "skip" && !servedBy && global.has(specifier)) continue;
4452
+ const tag = (servedBy ? builds.get(servedBy)?.tags.pinnedTag(specifier) : void 0) ?? version.tag;
4453
+ const file = servedBy && fileOf.get(servedBy)?.get(specifier);
4454
+ runs.set(
4455
+ specifier,
4456
+ file ? { tag, remote: servedBy, file } : { tag, remote: meta.name, file: meta.entries[specifier] }
3960
4457
  );
3961
- mine.forEach((member) => scope(member.name));
3962
- continue;
3963
4458
  }
3964
- redirect(entry.name, anchor, pool, asked.view, actions, shareScope);
3965
4459
  }
3966
- }
3967
- return Promise.resolve({ entry, actions });
4460
+ return runsOffMap2;
4461
+ }
4462
+ function judgeRemote(copies, view, accepts) {
4463
+ const { rejected, missing } = scan(copies, view, accepts);
4464
+ if (rejected) return { cause: "incompatible", ...rejected };
4465
+ const globalTags = new SpecifierTags([...view.global].map(([s, { tag }]) => [s, tag]));
4466
+ const binding = offMapBinding(copies, view, globalTags);
4467
+ if (binding) return { cause: "uncovered", ...binding };
4468
+ if (agrees(copies, globalTags)) return "global";
4469
+ if (missing !== void 0) return { cause: "uncovered", specifier: missing };
4470
+ if (unwitnessedGap(copies, globalTags, view.builds.values()) === void 0) return "global";
4471
+ return { cause: "uncovered", unshipped: true };
4472
+ }
4473
+ function coverFromMap(remote, mine, view, actions, policy) {
4474
+ const files = [];
4475
+ for (const member of mine) {
4476
+ const action = actions[member.name];
4477
+ const copy = member.external.versions.flatMap((v) => v.remotes).find((r) => r.name === remote);
4478
+ if (!copy) continue;
4479
+ const mapped = mapFiles(copy, view, policy, action);
4480
+ if ("unmapped" in mapped) return mapped;
4481
+ files.push({ member, action, copy, ...mapped });
4482
+ }
4483
+ const reaches = reachesOf(remote, files, view);
4484
+ const bound = boundByFile(reaches, view);
4485
+ if (bound !== void 0) return { bound };
4486
+ const servedPackages = new Set([...view.global.keys()].map(packageOf));
4487
+ const gap = files.map((file) => gapOf(file, servedPackages)).find((specifier) => specifier !== void 0);
4488
+ if (gap !== void 0) return { gap };
4489
+ return { covers: files.filter(covering).map(coverOf) };
4490
+ }
4491
+ function gapOf({ copy, covered }, servedPackages) {
4492
+ const entrypoints = Object.keys(copy.entries);
4493
+ const missing = entrypoints.filter((specifier) => !covered.has(specifier));
4494
+ if (missing.length < entrypoints.length) return missing[0];
4495
+ return missing.find((specifier) => servedPackages.has(packageOf(specifier)));
4496
+ }
4497
+ function covering({ action, copy, covered }) {
4498
+ if (action.action === "skip") return true;
4499
+ const entrypoints = Object.keys(copy.entries);
4500
+ return entrypoints.length > 0 && entrypoints.every((specifier) => covered.has(specifier));
4501
+ }
4502
+ function coverOf({ member, action, copy, covered, override }) {
4503
+ const toSkip = action.action !== "skip" || covered.size > action.covered.length && member.external.versions.some((v) => v.action === "share" && v.remotes.includes(copy));
4504
+ return { name: member.name, toSkip, override };
4505
+ }
4506
+ function reachesOf(remote, files, view) {
4507
+ const reaches = /* @__PURE__ */ new Map();
4508
+ const fromMap = (specifier) => {
4509
+ const served = view.global.get(specifier);
4510
+ return served && { remote: served.remote, file: served.file };
3968
4511
  };
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;
4512
+ for (const file of files) {
4513
+ const takesMap = covering(file);
4514
+ for (const [specifier, own] of Object.entries(file.copy.entries)) {
4515
+ if (!takesMap || !file.covered.has(specifier)) reaches.set(specifier, { remote, file: own });
4516
+ else {
4517
+ const reach = fromMap(specifier);
4518
+ if (reach) reaches.set(specifier, reach);
4012
4519
  }
4013
4520
  }
4014
- return ships2 > 0 && (winsAll || allScoped);
4015
4521
  }
4016
- function explainDynamic(remote, { view, wants, specifiers, acceptance }) {
4017
- return explainSelfServe(remote, wants, specifiers, {
4018
- builds: view.builds,
4019
- acceptance: acceptance()
4020
- });
4522
+ return reaches;
4523
+ }
4524
+ function boundByFile(reaches, view) {
4525
+ const servers = /* @__PURE__ */ new Set();
4526
+ for (const { remote } of reaches.values()) if (view.builds.has(remote)) servers.add(remote);
4527
+ const firstRun = /* @__PURE__ */ new Map();
4528
+ for (const build of servers)
4529
+ for (const specifier of view.builds.get(build).tags.keys()) {
4530
+ const served = view.runsOffMap.get(build)?.get(specifier) ?? view.global.get(specifier);
4531
+ if (served === void 0) continue;
4532
+ const reached = reaches.get(specifier) ?? firstRun.get(specifier);
4533
+ if (reached === void 0) {
4534
+ firstRun.set(specifier, served);
4535
+ if (view.builds.has(served.remote)) servers.add(served.remote);
4536
+ } else if (reached.remote !== served.remote || reached.file !== served.file)
4537
+ return { owner: build, specifier, tag: served.tag };
4538
+ }
4539
+ return void 0;
4540
+ }
4541
+ function mapFiles(copy, view, policy, action) {
4542
+ const covered = new Set(action.covered);
4543
+ const override = { ...action.override };
4544
+ for (const specifier in copy.entries) {
4545
+ const source = view.global.get(specifier);
4546
+ if (!source) continue;
4547
+ if (policy.shareScope !== GLOBAL_SCOPE) {
4548
+ const scopeUrl = policy.scopeUrlOf(source.remote);
4549
+ if (!scopeUrl) return { unmapped: source.remote };
4550
+ override[specifier] = join(scopeUrl, source.file);
4551
+ }
4552
+ covered.add(specifier);
4021
4553
  }
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;
4554
+ return { covered, override };
4555
+ }
4556
+ function scan(copies, view, accepts) {
4557
+ let rejected;
4558
+ let missing;
4559
+ for (const copy of copies)
4560
+ for (const s of copy.specifiers) {
4561
+ const global = view.global.get(s);
4562
+ if (global === void 0) missing ??= s;
4563
+ else if (!accepts(global.tag, copy.tag, copy.requiredVersion))
4564
+ rejected ??= { specifier: s, tag: global.tag };
4031
4565
  }
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);
4566
+ return { rejected, missing };
4567
+ }
4568
+ function offMapBinding(copies, view, globalTags) {
4569
+ const own = new SpecifierTags(copies.flatMap((c) => c.specifiers.map((s) => [s, c.tag])));
4570
+ const servers = /* @__PURE__ */ new Set();
4571
+ for (const copy of copies)
4572
+ for (const specifier of copy.specifiers) {
4573
+ const build = view.global.get(specifier)?.remote;
4574
+ if (build !== void 0) servers.add(build);
4575
+ }
4576
+ const firstRun = new SpecifierTags();
4577
+ for (const build of servers)
4578
+ for (const [specifier, { tag }] of view.runsOffMap.get(build) ?? []) {
4579
+ const reached = globalTags.pinnedTag(specifier) ?? own.pinnedTag(specifier) ?? firstRun.pinnedTag(specifier);
4580
+ if (reached === void 0) firstRun.set(specifier, tag);
4581
+ else if (reached !== tag) return { owner: build, specifier, tag };
4582
+ }
4583
+ return void 0;
4584
+ }
4585
+ function inRecordOrder(members, recordOrder) {
4586
+ const rank2 = new Map(recordOrder.map((name, i) => [name, i]));
4587
+ return [...members].sort((a, b) => rank2.get(a.name) - rank2.get(b.name)).map((m) => [m.name, m.external]);
4588
+ }
4589
+
4590
+ // src/lib/core/2.app/steps/pooling/pool-dynamic-externals.ts
4591
+ function createPoolDynamicExternals(config, ports) {
4592
+ const accepts = createAcceptsTag(
4593
+ (tag, range) => ports.versionCheck.isCompatible(tag, range),
4594
+ (a, b) => ports.versionCheck.compare(a, b)
4595
+ );
4596
+ const scopeUrlOf = (remote) => ports.remoteInfoRepo.tryGet(remote).get()?.scopeUrl;
4597
+ return ({ entry, actions }) => {
4598
+ for (const [scope, names] of poolableNames(entry, actions)) {
4599
+ const committed = ports.sharedExternalsRepo.getFromScope(scope);
4600
+ if (!scopeHasPoolState(committed)) continue;
4601
+ const { pools } = buildPools(committed);
4602
+ const moves = judgeScope(entry.name, names, actions, pools, Object.keys(committed), scope);
4603
+ writeMoves(entry.name, names, moves, committed, pools, scope);
4604
+ }
4605
+ return Promise.resolve({ entry, actions: publicActions(actions) });
4606
+ };
4607
+ function judgeScope(remote, names, actions, pools, recordOrder, scope) {
4608
+ const policy = { shareScope: scope, scopeUrlOf };
4609
+ const moves = /* @__PURE__ */ new Map();
4610
+ const selfServe = (members, cause) => {
4611
+ for (const { name } of members) {
4612
+ actions[name].action = "scope";
4613
+ delete actions[name].override;
4614
+ moves.set(name, { cause });
4615
+ }
4616
+ };
4617
+ for (const pool of pools.values()) {
4618
+ const mine = pool.filter((member) => names.has(member.name));
4619
+ if (mine.length === 0) continue;
4620
+ const shipped = copiesByRemote(pool);
4621
+ const view = committedView(pool, shipped, recordOrder, {
4622
+ shareScope: scope,
4623
+ without: remote
4624
+ });
4625
+ const own = shipped.get(remote) ?? [];
4626
+ const verdict = judgeRemote(own, view, accepts);
4627
+ if (verdict !== "global") {
4628
+ config.log.warn(8, `[${scope}] ${selfServeWarning(remote, verdict, specifierCount(own))}`);
4629
+ selfServe(mine, verdict.cause);
4630
+ continue;
4631
+ }
4632
+ const cover = coverFromMap(remote, mine, view, actions, policy);
4633
+ if (!("covers" in cover)) {
4634
+ if ("unmapped" in cover) warnUnmapped(scope, remote, cover.unmapped);
4635
+ else
4636
+ config.log.warn(
4637
+ 8,
4638
+ `[${scope}] ${selfServeWarning(remote, coverMiss(cover), specifierCount(own))}`
4639
+ );
4640
+ selfServe(mine, "uncovered");
4641
+ continue;
4642
+ }
4643
+ for (const { name, toSkip, override } of cover.covers) {
4644
+ const action = actions[name];
4645
+ if (toSkip) {
4646
+ action.action = "skip";
4647
+ moves.set(name, { fromMap: true });
4648
+ }
4649
+ if (Object.keys(override).length > 0) action.override = override;
4044
4650
  }
4045
- action.covered = specifiers;
4046
- if (Object.keys(override).length > 0) action.override = override;
4047
4651
  }
4652
+ return moves;
4653
+ }
4654
+ function writeMoves(remote, names, moves, committed, pools, scope) {
4655
+ const written = {};
4656
+ for (const [name, move] of moves)
4657
+ written[name] = movedRecord(committed[name], remote, move, ports.versionCheck.compare);
4658
+ for (const [name, record] of renamedRecords({ ...committed, ...written }, pools))
4659
+ written[name] = record;
4660
+ if (!config.strict.strictExternalCompatibility)
4661
+ for (const { name } of [...pools.values()].flat().filter((m) => names.has(m.name)))
4662
+ written[name] = { ...written[name] ?? committed[name], dirty: true };
4663
+ for (const [name, record] of Object.entries(written))
4664
+ ports.sharedExternalsRepo.addOrUpdate(name, record, scope);
4665
+ }
4666
+ function warnUnmapped(scope, remote, build) {
4667
+ config.log.warn(
4668
+ 8,
4669
+ `[${scope}][${remote}] '${build}' is not in the cache, so its files cannot be mapped.`
4670
+ );
4048
4671
  }
4049
4672
  }
4673
+ function publicActions(actions) {
4674
+ for (const action of Object.values(actions)) delete action.covered;
4675
+ return actions;
4676
+ }
4677
+ function poolableNames(entry, actions) {
4678
+ const declared = /* @__PURE__ */ new Map();
4679
+ for (const external of entry.shared ?? []) {
4680
+ const name = external.packageName;
4681
+ if (!external.singleton || !actions[name]) continue;
4682
+ if (external.shareScope === STRICT_SCOPE) continue;
4683
+ const scope = external.shareScope ?? GLOBAL_SCOPE;
4684
+ let names = declared.get(scope);
4685
+ if (!names) declared.set(scope, names = /* @__PURE__ */ new Set());
4686
+ names.add(name);
4687
+ }
4688
+ return declared;
4689
+ }
4690
+ function coverMiss(cover) {
4691
+ if ("gap" in cover) return { cause: "uncovered", specifier: cover.gap };
4692
+ return { cause: "uncovered", ...cover.bound };
4693
+ }
4694
+ function selfServeWarning(remote, miss, imports) {
4695
+ const where = `All ${imports} of its entrypoints are scoped for it.`;
4696
+ if (miss.cause === "incompatible")
4697
+ return `'${remote}' is islanded: its range rejects '${miss.specifier}@${miss.tag}' of the committed map. ${where}`;
4698
+ if ("owner" in miss)
4699
+ return `'${remote}' serves its own family: committed build '${miss.owner}', whose files it would reach, resolves '${miss.specifier}' to another file than it would. ${where}`;
4700
+ if ("unshipped" in miss)
4701
+ return `'${remote}' serves its own family: no committed build shipped the map's combination for it. ${where}`;
4702
+ return `'${remote}' serves its own family: no committed build offers every entrypoint it imports at a version it accepts (gap '${miss.specifier}'). ${where}`;
4703
+ }
4050
4704
 
4051
4705
  // src/lib/core/2.app/steps/convert-to-import-map.ts
4052
4706
  function createConvertToImportMap(config, ports) {
@@ -4083,27 +4737,14 @@ function createConvertToImportMap(config, ports) {
4083
4737
  return;
4084
4738
  }
4085
4739
  if (actions[external.packageName].action === "skip") {
4086
- const { override, covered, sameVersion } = actions[external.packageName];
4740
+ const { override } = actions[external.packageName];
4087
4741
  if (override) {
4088
4742
  Object.entries(override).forEach(([packageName, url]) => {
4089
4743
  addToScopes(remoteEntryScope, packageName, url, importMap);
4090
4744
  });
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
- }
4105
4745
  return;
4106
4746
  }
4747
+ if (!external.shareScope) return;
4107
4748
  log.error(
4108
4749
  9,
4109
4750
  `[${remoteEntry.name}][${external.packageName}] shareScope skip has no override.`
@@ -4168,27 +4809,6 @@ function createConvertToImportMap(config, ports) {
4168
4809
  });
4169
4810
  return importMap;
4170
4811
  }
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
- }
4184
- function warnUncoveredEntrypoint(remoteName, externalName, packageName) {
4185
- const msg = `[${remoteName}][${externalName}] Entrypoint '${packageName}' is not covered by the override.`;
4186
- if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
4187
- log.error(9, msg);
4188
- throw new NFError("Could not create ImportMap.");
4189
- }
4190
- log.warn(9, msg);
4191
- }
4192
4812
  function addIntegrity(importMap, url, integrityMap, file) {
4193
4813
  const hash = integrityMap?.[file];
4194
4814
  if (!hash) return;
@@ -4204,11 +4824,12 @@ var createInitDrivers = ({
4204
4824
  }) => ({
4205
4825
  getRemoteEntries: createGetRemoteEntries(config, adapters),
4206
4826
  processRemoteEntries: createProcessRemoteEntries(config, adapters),
4207
- markPoolsForReelection: createMarkPoolsForReelection(config, adapters),
4208
4827
  determineSharedExternals: createDetermineSharedExternals(config, adapters),
4209
4828
  poolSharedExternals: createPoolSharedExternals(config, adapters),
4829
+ decideServingBuilds: createDecideServingBuilds(config, adapters),
4210
4830
  generateImportMap: createGenerateImportMap(config, adapters),
4211
4831
  commitChanges: createCommitChanges(config, adapters),
4832
+ rollbackChanges: createRollbackChanges(config, adapters),
4212
4833
  exposeModuleLoader: createExposeModuleLoader(config, adapters),
4213
4834
  getRemoteEntry: createGetRemoteEntry(config, adapters),
4214
4835
  updateCache: createUpdateCache(config, adapters),
@@ -4228,14 +4849,6 @@ var INIT_FLOW_FACTORY = ({
4228
4849
  };
4229
4850
 
4230
4851
  // src/lib/core/5.di/federation-result.factory.ts
4231
- var createStateDump = (config, adapters) => (msg) => config.log.debug(0, msg, {
4232
- remotes: { ...adapters.remoteInfoRepo.getAll() },
4233
- "shared-externals": adapters.sharedExternalsRepo.getScopes({ includeGlobal: true }).reduce(
4234
- (acc, scope) => ({ ...acc, [scope]: adapters.sharedExternalsRepo.getFromScope(scope) }),
4235
- {}
4236
- ),
4237
- "scoped-externals": adapters.scopedExternalsRepo.getAll()
4238
- });
4239
4852
  var createFederationResult = ({
4240
4853
  config,
4241
4854
  adapters,
@@ -4243,11 +4856,8 @@ var createFederationResult = ({
4243
4856
  initRemoteEntryFlow,
4244
4857
  afterInitRemoteEntry
4245
4858
  }) => {
4246
- const stateDump = createStateDump(config, adapters);
4247
4859
  const initRemoteEntry = async (remoteEntryUrl, remote) => {
4248
- const remoteName = typeof remote === "string" ? remote : remote?.name;
4249
4860
  return initRemoteEntryFlow(remoteEntryUrl, remote).catch((e) => {
4250
- stateDump(`[dynamic-init][${remoteName ?? remoteEntryUrl}] STATE DUMP`);
4251
4861
  if (config.strict.strictRemoteEntry) return Promise.reject(e);
4252
4862
  else console.warn("Failed to initialize remote entry, continuing anyway.");
4253
4863
  return Promise.resolve();