@softarc/native-federation-orchestrator 4.7.0 → 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 (72) hide show
  1. package/fesm2022/@softarc/native-federation-orchestrator.mjs +1810 -1359
  2. package/fesm2022/@softarc/native-federation-orchestrator.mjs.map +4 -4
  3. package/fesm2022/audit.mjs +1 -1
  4. package/fesm2022/audit.mjs.map +2 -2
  5. package/fesm2022/module-federation.mjs +199 -32
  6. package/fesm2022/module-federation.mjs.map +3 -3
  7. package/fesm2022/node.mjs +1804 -1353
  8. package/fesm2022/node.mjs.map +4 -4
  9. package/fesm2022/options.mjs +1 -3
  10. package/fesm2022/options.mjs.map +2 -2
  11. package/fesm2022/registry.mjs +1 -1
  12. package/fesm2022/registry.mjs.map +2 -2
  13. package/fesm2022/sdk.mjs +1 -1
  14. package/fesm2022/sdk.mjs.map +2 -2
  15. package/init-registry.mjs +1 -1
  16. package/package.json +9 -1
  17. package/quickstart.mjs +14 -15
  18. package/types/lib/core/1.domain/externals/basis.d.ts +2 -15
  19. package/types/lib/core/1.domain/externals/claims.d.ts +22 -0
  20. package/types/lib/core/1.domain/externals/compatibility.d.ts +9 -0
  21. package/types/lib/core/1.domain/externals/external.contract.d.ts +3 -0
  22. package/types/lib/core/1.domain/externals/rows.d.ts +9 -0
  23. package/types/lib/core/1.domain/externals/serving.d.ts +52 -0
  24. package/types/lib/core/1.domain/externals/specifier.d.ts +13 -0
  25. package/types/lib/core/1.domain/externals/version.contract.d.ts +1 -1
  26. package/types/lib/core/1.domain/pooling/builds.d.ts +20 -0
  27. package/types/lib/core/1.domain/pooling/election.d.ts +40 -0
  28. package/types/lib/core/1.domain/pooling/gate.d.ts +62 -0
  29. package/types/lib/core/1.domain/pooling/membership.d.ts +17 -0
  30. package/types/lib/core/1.domain/pooling/plan.d.ts +9 -0
  31. package/types/lib/core/1.domain/pooling/pool-state.d.ts +4 -0
  32. package/types/lib/core/1.domain/pooling/records.d.ts +16 -0
  33. package/types/lib/core/1.domain/pooling/rules.d.ts +6 -0
  34. package/types/lib/core/1.domain/remote-entry/load-actions.contract.d.ts +4 -0
  35. package/types/lib/core/1.domain/remote-entry/remote-entry.contract.d.ts +0 -2
  36. package/types/lib/core/2.app/config/mode.contract.d.ts +8 -4
  37. package/types/lib/core/2.app/config/storage.contract.d.ts +6 -1
  38. package/types/lib/core/2.app/driver-ports/init/drivers.contract.d.ts +4 -2
  39. package/types/lib/core/2.app/driver-ports/init/for-committing-changes.port.d.ts +1 -0
  40. package/types/lib/core/2.app/driver-ports/init/for-deciding-serving-builds.port.d.ts +1 -0
  41. package/types/lib/core/2.app/driver-ports/init/for-determining-shared-externals.port.d.ts +1 -8
  42. package/types/lib/core/2.app/driver-ports/init/for-pooling-dynamic-externals.port.d.ts +2 -1
  43. package/types/lib/core/2.app/driver-ports/init/for-pooling-shared-externals.port.d.ts +1 -3
  44. package/types/lib/core/2.app/driver-ports/init/for-rolling-back-changes.port.d.ts +1 -0
  45. package/types/lib/core/2.app/driver-ports/init/for-updating-cache.d.ts +3 -2
  46. package/types/lib/core/2.app/driver-ports/init/index.d.ts +2 -0
  47. package/types/lib/core/2.app/driving-ports/for-remote-info-storage.port.d.ts +1 -0
  48. package/types/lib/core/2.app/driving-ports/for-scoped-externals-storage.port.d.ts +1 -0
  49. package/types/lib/core/2.app/driving-ports/for-shared-chunks-storage.port.d.ts +1 -0
  50. package/types/lib/core/2.app/driving-ports/for-shared-externals-storage.port.d.ts +5 -1
  51. package/types/lib/core/2.app/steps/apply-winner.d.ts +6 -13
  52. package/types/lib/core/2.app/steps/decide-serving-builds.d.ts +5 -0
  53. package/types/lib/core/2.app/steps/pooling/pool-shared-externals.d.ts +0 -29
  54. package/types/lib/core/2.app/steps/rollback-changes.d.ts +5 -0
  55. package/types/lib/core/2.app/steps/store-remote-entry.d.ts +2 -2
  56. package/types/lib/core/3.adapters/storage/cache-state.d.ts +5 -0
  57. package/types/lib/core/4.config/orchestrator-version.d.ts +1 -0
  58. package/types/lib/core/4.config/storage/storage.config.d.ts +1 -1
  59. package/types/lib/core/5.di/federation-result.factory.d.ts +1 -1
  60. package/types/lib/sdk.index.d.ts +4 -0
  61. package/types/lib/utils/compare-strings.d.ts +2 -0
  62. package/types/lib/utils/deep-readonly.d.ts +3 -0
  63. package/types/lib/core/2.app/driver-ports/init/for-marking-pools-for-reelection.port.d.ts +0 -6
  64. package/types/lib/core/2.app/steps/pooling/anchoring.d.ts +0 -69
  65. package/types/lib/core/2.app/steps/pooling/mark-pools-for-reelection.d.ts +0 -5
  66. package/types/lib/core/2.app/steps/pooling/pool-graph.d.ts +0 -25
  67. package/types/lib/core/2.app/steps/pooling/pool-views.d.ts +0 -63
  68. package/types/lib/core/2.app/steps/pooling/pool.types.d.ts +0 -35
  69. package/types/lib/core/2.app/steps/pooling/pool.util.d.ts +0 -19
  70. package/types/lib/testing/handlers/log.handler.d.ts +0 -3
  71. package/types/lib/testing/pooling/no-tear.d.ts +0 -37
  72. package/types/lib/testing/pooling/tag-by-npm-scope.d.ts +0 -9
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
  };
@@ -1435,7 +1446,8 @@ var STORAGE_KEYS = {
1435
1446
  remotes: "remotes",
1436
1447
  sharedExternals: "shared-externals",
1437
1448
  scopedExternals: "scoped-externals",
1438
- sharedChunks: "shared-chunks"
1449
+ sharedChunks: "shared-chunks",
1450
+ cacheState: "cache-state"
1439
1451
  };
1440
1452
 
1441
1453
  // src/lib/utils/path.ts
@@ -1497,7 +1509,7 @@ var Optional = class _Optional {
1497
1509
  var createRemoteInfoRepository = (config) => {
1498
1510
  const STORAGE = config.storage(STORAGE_KEYS.remotes, {});
1499
1511
  if (config.clearStorage) STORAGE.clear();
1500
- const _cache = STORAGE.get() ?? {};
1512
+ let _cache = STORAGE.get() ?? {};
1501
1513
  let _dirty = false;
1502
1514
  return {
1503
1515
  contains: function(remoteName) {
@@ -1531,6 +1543,11 @@ var createRemoteInfoRepository = (config) => {
1531
1543
  STORAGE.set(_cache);
1532
1544
  _dirty = false;
1533
1545
  return this;
1546
+ },
1547
+ rollback: function() {
1548
+ _cache = STORAGE.get() ?? {};
1549
+ _dirty = false;
1550
+ return this;
1534
1551
  }
1535
1552
  };
1536
1553
  };
@@ -1539,7 +1556,7 @@ var createRemoteInfoRepository = (config) => {
1539
1556
  var createScopedExternalsRepository = (config) => {
1540
1557
  const STORAGE = config.storage(STORAGE_KEYS.scopedExternals, {});
1541
1558
  if (config.clearStorage) STORAGE.clear();
1542
- const _cache = STORAGE.get() ?? {};
1559
+ let _cache = STORAGE.get() ?? {};
1543
1560
  let _dirty = false;
1544
1561
  return {
1545
1562
  addExternal: function(remoteName, external, version) {
@@ -1566,6 +1583,11 @@ var createScopedExternalsRepository = (config) => {
1566
1583
  STORAGE.set(_cache);
1567
1584
  _dirty = false;
1568
1585
  return this;
1586
+ },
1587
+ rollback: function() {
1588
+ _cache = STORAGE.get() ?? {};
1589
+ _dirty = false;
1590
+ return this;
1569
1591
  }
1570
1592
  };
1571
1593
  };
@@ -1574,39 +1596,48 @@ var createScopedExternalsRepository = (config) => {
1574
1596
  var GLOBAL_SCOPE = "__GLOBAL__";
1575
1597
  var STRICT_SCOPE = "strict";
1576
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
+
1577
1617
  // src/lib/core/3.adapters/storage/shared-externals.repository.ts
1578
1618
  var createSharedExternalsRepository = (config) => {
1579
1619
  const STORAGE = config.storage(
1580
1620
  STORAGE_KEYS.sharedExternals,
1581
1621
  { [GLOBAL_SCOPE]: {} }
1582
1622
  );
1583
- if (config.clearStorage) STORAGE.clear();
1584
- const _cache = STORAGE.get() ?? { [GLOBAL_SCOPE]: {} };
1585
- 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;
1586
1633
  return {
1587
- // Read from the cache rather than remembered from this init's entries: a warm init may not refetch
1588
- // the tagged remote at all, and pooling has to coordinate its pool anyway. Exits on the first hit.
1589
- // Per share scope, because a pool never spans one: a tag in another scope is no reason to pool here.
1590
- // Stored pool results count too, since a scope whose last tag left still has pool state to clear.
1591
- hasPoolState: function(shareScope) {
1592
- const scope = _cache[shareScope ?? GLOBAL_SCOPE];
1593
- if (!scope) return false;
1594
- for (const external of Object.values(scope)) {
1595
- if (external.poolName !== void 0) return true;
1596
- for (const version of external.versions)
1597
- for (const remote of version.remotes)
1598
- if (remote.pool?.trim() || remote.servedBy !== void 0 || remote.poolCause !== void 0)
1599
- return true;
1600
- }
1601
- return false;
1602
- },
1603
1634
  getFromScope: function(shareScope) {
1604
1635
  return { ..._cache[shareScope ?? GLOBAL_SCOPE] };
1605
1636
  },
1606
1637
  addOrUpdate: function(externalName, external, shareScope) {
1607
1638
  if (!_cache[shareScope ?? GLOBAL_SCOPE]) _cache[shareScope ?? GLOBAL_SCOPE] = {};
1608
1639
  _cache[shareScope ?? GLOBAL_SCOPE][externalName] = external;
1609
- _dirty = true;
1640
+ _uncommitted.add(shareScope ?? GLOBAL_SCOPE);
1610
1641
  return this;
1611
1642
  },
1612
1643
  getScopes: function(o = { includeGlobal: true }) {
@@ -1616,7 +1647,7 @@ var createSharedExternalsRepository = (config) => {
1616
1647
  // Batched: one traversal of the graph per init instead of one per overridden remote.
1617
1648
  removeFromAllScopes: function(remoteNames) {
1618
1649
  if (remoteNames.size === 0) return;
1619
- Object.values(_cache).forEach((scope) => {
1650
+ Object.entries(_cache).forEach(([scopeName, scope]) => {
1620
1651
  const removeExternals = [];
1621
1652
  Object.entries(scope).forEach(([name, external]) => {
1622
1653
  let removedCopy = false;
@@ -1632,12 +1663,12 @@ var createSharedExternalsRepository = (config) => {
1632
1663
  if (keep !== remotes.length) {
1633
1664
  remotes.length = keep;
1634
1665
  removedCopy = true;
1635
- _dirty = true;
1666
+ _uncommitted.add(scopeName);
1636
1667
  if (host !== void 0 && remoteNames.has(host)) version.host = false;
1637
1668
  }
1638
1669
  if (remotes.length === 0) {
1639
1670
  external.versions.splice(i, 1);
1640
- _dirty = true;
1671
+ _uncommitted.add(scopeName);
1641
1672
  }
1642
1673
  }
1643
1674
  if (removedCopy) {
@@ -1645,7 +1676,17 @@ var createSharedExternalsRepository = (config) => {
1645
1676
  if (external.versions.length === 0) removeExternals.push(name);
1646
1677
  }
1647
1678
  });
1648
- 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
+ }
1649
1690
  });
1650
1691
  },
1651
1692
  scopeType: function(shareScope) {
@@ -1663,10 +1704,36 @@ var createSharedExternalsRepository = (config) => {
1663
1704
  tryGet: function(external, shareScope) {
1664
1705
  return Optional.of(_cache[shareScope ?? GLOBAL_SCOPE]?.[external]);
1665
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.
1666
1723
  commit: function() {
1667
- if (!_dirty) return this;
1668
- STORAGE.set(_cache);
1669
- _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;
1670
1737
  return this;
1671
1738
  }
1672
1739
  };
@@ -1676,7 +1743,7 @@ var createSharedExternalsRepository = (config) => {
1676
1743
  var createChunkRepository = (config) => {
1677
1744
  const STORAGE = config.storage(STORAGE_KEYS.sharedChunks, {});
1678
1745
  if (config.clearStorage) STORAGE.clear();
1679
- const _cache = STORAGE.get() ?? {};
1746
+ let _cache = STORAGE.get() ?? {};
1680
1747
  let _dirty = false;
1681
1748
  return {
1682
1749
  addOrReplace: function(remoteName, bundleName, chunks) {
@@ -1701,6 +1768,11 @@ var createChunkRepository = (config) => {
1701
1768
  STORAGE.set(_cache);
1702
1769
  _dirty = false;
1703
1770
  return this;
1771
+ },
1772
+ rollback: function() {
1773
+ _cache = STORAGE.get() ?? {};
1774
+ _dirty = false;
1775
+ return this;
1704
1776
  }
1705
1777
  };
1706
1778
  };
@@ -1907,8 +1979,10 @@ var globalThisStorageEntry = (namespace) => (key, initialValue) => {
1907
1979
  globalThisStorageEntry.type = "globalThis";
1908
1980
  Object.freeze(globalThisStorageEntry);
1909
1981
 
1982
+ // src/lib/core/4.config/orchestrator-version.ts
1983
+ var ORCHESTRATOR_VERSION = true ? "4.8.0" : "dev";
1984
+
1910
1985
  // src/lib/core/4.config/storage/publish-storage-info.ts
1911
- var VERSION = true ? "4.7.0" : "dev";
1912
1986
  var KEYS = Object.freeze(Object.values(STORAGE_KEYS));
1913
1987
  var isStorageKey = (key) => KEYS.includes(key);
1914
1988
  var createStorageInfo = ({
@@ -1917,7 +1991,12 @@ var createStorageInfo = ({
1917
1991
  storage,
1918
1992
  exposeGetter
1919
1993
  }) => {
1920
- const info = { version: VERSION, type, namespace, keys: KEYS };
1994
+ const info = {
1995
+ version: ORCHESTRATOR_VERSION,
1996
+ type,
1997
+ namespace,
1998
+ keys: KEYS
1999
+ };
1921
2000
  if (!exposeGetter) return Object.freeze(info);
1922
2001
  return Object.freeze({
1923
2002
  ...info,
@@ -1938,6 +2017,12 @@ var publishStorageInfo = (options) => {
1938
2017
  };
1939
2018
 
1940
2019
  // src/lib/core/4.config/storage/storage.config.ts
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
+ };
1941
2026
  var createStorageConfig = (override) => {
1942
2027
  const creator = override.storage ?? globalThisStorageEntry;
1943
2028
  const namespace = override.storageNamespace ?? "__NATIVE_FEDERATION__";
@@ -1948,9 +2033,10 @@ var createStorageConfig = (override) => {
1948
2033
  storage,
1949
2034
  exposeGetter: override.exposeStorageGetter ?? true
1950
2035
  });
2036
+ const outdated = isOutdatedCache(storage);
1951
2037
  return {
1952
2038
  storage,
1953
- clearStorage: override.clearStorage ?? false
2039
+ clearStorage: (override.clearStorage ?? false) || outdated
1954
2040
  };
1955
2041
  };
1956
2042
 
@@ -1958,7 +2044,6 @@ var createStorageConfig = (override) => {
1958
2044
  var defaultProfile = {
1959
2045
  latestSharedExternal: false,
1960
2046
  skipInvalidExternalVersions: false,
1961
- scopeUncoveredEntrypoints: false,
1962
2047
  overrideCachedRemotes: "init-only",
1963
2048
  overrideCachedRemotesIfURLMatches: false
1964
2049
  };
@@ -1970,15 +2055,13 @@ var createModeConfig = (override) => {
1970
2055
  strictExternalCompatibility: override.strict,
1971
2056
  strictExternalSameVersionCompatibility: override.strict,
1972
2057
  strictExternalVersion: override.strict,
1973
- strictImportMap: override.strict,
1974
- strictEntryPointCoverage: override.strict
2058
+ strictImportMap: override.strict
1975
2059
  } : {
1976
2060
  strictRemoteEntry: override.strict?.strictRemoteEntry ?? false,
1977
2061
  strictExternalCompatibility: override.strict?.strictExternalCompatibility ?? false,
1978
2062
  strictExternalSameVersionCompatibility: override.strict?.strictExternalSameVersionCompatibility ?? false,
1979
2063
  strictExternalVersion: override.strict?.strictExternalVersion ?? false,
1980
- strictImportMap: override.strict?.strictImportMap ?? false,
1981
- strictEntryPointCoverage: override.strict?.strictEntryPointCoverage ?? false
2064
+ strictImportMap: override.strict?.strictImportMap ?? false
1982
2065
  };
1983
2066
  return {
1984
2067
  strict: strictnessConfig,
@@ -2004,7 +2087,7 @@ var createInitFlow = ({
2004
2087
  adapters,
2005
2088
  config
2006
2089
  }) => {
2007
- 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) => ({
2008
2091
  config,
2009
2092
  adapters,
2010
2093
  loadRemoteModule
@@ -2015,8 +2098,11 @@ var createInitFlow = ({
2015
2098
  var createInitRemoteEntryFlow = ({
2016
2099
  flow
2017
2100
  }) => {
2018
- const processDynamicRemoteEntry = (remoteEntry) => flow.updateCache(remoteEntry).then(flow.poolDynamicExternals).then(flow.convertToImportMap).then(flow.commitChanges);
2019
- 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
+ });
2020
2106
  };
2021
2107
 
2022
2108
  // src/lib/core/2.app/steps/get-remote-entries.ts
@@ -2123,11 +2209,17 @@ function createGetRemoteEntries(config, ports) {
2123
2209
  }
2124
2210
  }
2125
2211
 
2212
+ // src/lib/utils/compare-strings.ts
2213
+ function compareStrings(a, b) {
2214
+ return a < b ? -1 : a > b ? 1 : 0;
2215
+ }
2216
+
2126
2217
  // src/lib/core/1.domain/externals/basis.ts
2127
2218
  var coverage = (remote) => Object.keys(remote.entries).length;
2128
2219
  function isBetterBasis(candidate, basis) {
2129
2220
  if (basis.cached !== candidate.cached) return candidate.cached;
2130
- return coverage(candidate) > coverage(basis);
2221
+ const byCoverage = coverage(candidate) - coverage(basis);
2222
+ return byCoverage === 0 ? compareStrings(candidate.name, basis.name) < 0 : byCoverage > 0;
2131
2223
  }
2132
2224
  function addRemoteToVersion(version, remote, isHost = false) {
2133
2225
  if (isHost) {
@@ -2151,21 +2243,6 @@ function versionDemands(version) {
2151
2243
  }
2152
2244
  return Array.from(distinct.values());
2153
2245
  }
2154
- function findVersionForTag(versions, tag) {
2155
- let scoped;
2156
- for (const version of versions) {
2157
- if (version.tag !== tag) continue;
2158
- if (version.action !== "scope") return version;
2159
- scoped ??= version;
2160
- }
2161
- return scoped;
2162
- }
2163
- function versionEntries(version) {
2164
- return collectEntries(version, void 0);
2165
- }
2166
- function committedEntries(version) {
2167
- return collectEntries(version, (remote) => remote.cached);
2168
- }
2169
2246
  function forEachVersionEntry(version, accepts, visit) {
2170
2247
  for (const remote of version.remotes) {
2171
2248
  if (remote.servedBy) continue;
@@ -2173,16 +2250,6 @@ function forEachVersionEntry(version, accepts, visit) {
2173
2250
  for (const entrypoint in remote.entries) visit(entrypoint, remote);
2174
2251
  }
2175
2252
  }
2176
- function collectEntries(version, accepts) {
2177
- const entries = /* @__PURE__ */ new Map();
2178
- forEachVersionEntry(version, accepts, (entrypoint, remote) => {
2179
- if (!entries.has(entrypoint)) entries.set(entrypoint, remote);
2180
- });
2181
- return entries;
2182
- }
2183
- function uncoveredEntrypoints(remote, covered) {
2184
- return Object.keys(remote.entries).filter((entrypoint) => !covered.has(entrypoint));
2185
- }
2186
2253
  function countUncoveredEntrypoints(remote, covered) {
2187
2254
  let uncovered = 0;
2188
2255
  for (const entrypoint in remote.entries) {
@@ -2191,6 +2258,89 @@ function countUncoveredEntrypoints(remote, covered) {
2191
2258
  return uncovered;
2192
2259
  }
2193
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
+
2194
2344
  // src/lib/core/2.app/steps/store-remote-entry.ts
2195
2345
  function createRemoveCachedRemoteEntries(ports) {
2196
2346
  return (remoteNames) => {
@@ -2224,6 +2374,11 @@ function createStoreRemoteEntry(config, ports, logStep) {
2224
2374
  const tag = resolveVersion(remoteEntry, external);
2225
2375
  if (tag === null) return;
2226
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
+ );
2227
2382
  onSharedExternal(remoteEntry, external, sharedExternalContext(remoteEntry, external, tag));
2228
2383
  } else {
2229
2384
  addScopedExternal(remoteEntry, external, tag);
@@ -2274,11 +2429,12 @@ function createStoreRemoteEntry(config, ports, logStep) {
2274
2429
  ...sharedInfo.pool?.trim() ? { pool: sharedInfo.pool } : {},
2275
2430
  entries: sharedInfo.entries
2276
2431
  };
2277
- const cached2 = ports.sharedExternalsRepo.tryGet(sharedInfo.packageName, sharedInfo.shareScope).get() ?? { dirty: false, versions: [] };
2432
+ let stored;
2433
+ const readRecord = () => stored ??= ports.sharedExternalsRepo.tryGet(sharedInfo.packageName, sharedInfo.shareScope).get() ?? { dirty: false, versions: [] };
2278
2434
  return {
2279
2435
  tag,
2280
2436
  remote,
2281
- cached: cached2,
2437
+ readRecord,
2282
2438
  scopeType,
2283
2439
  assertSameVersionCompatibility(matchingVersion) {
2284
2440
  if (!remote.strictVersion) return;
@@ -2290,13 +2446,12 @@ function createStoreRemoteEntry(config, ports, logStep) {
2290
2446
  }
2291
2447
  config.log.warn(logStep, errorMsg);
2292
2448
  },
2293
- commit() {
2449
+ write() {
2294
2450
  ports.sharedExternalsRepo.addOrUpdate(
2295
2451
  sharedInfo.packageName,
2296
2452
  {
2297
- dirty: cached2.dirty,
2298
- ...cached2.poolName !== void 0 && { poolName: cached2.poolName },
2299
- 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))
2300
2455
  },
2301
2456
  sharedInfo.shareScope
2302
2457
  );
@@ -2340,11 +2495,12 @@ function createProcessRemoteEntries(config, ports) {
2340
2495
  function addSharedExternal(remoteEntry, _sharedInfo, {
2341
2496
  tag,
2342
2497
  remote,
2343
- cached: cached2,
2498
+ readRecord,
2344
2499
  scopeType,
2345
2500
  assertSameVersionCompatibility,
2346
- commit
2501
+ write
2347
2502
  }) {
2503
+ const cached2 = readRecord();
2348
2504
  if (scopeType !== "strict") cached2.dirty = true;
2349
2505
  const matchingVersion = findVersionForTag(cached2.versions, tag);
2350
2506
  if (matchingVersion) {
@@ -2358,158 +2514,74 @@ function createProcessRemoteEntries(config, ports) {
2358
2514
  remotes: [remote]
2359
2515
  });
2360
2516
  }
2361
- commit();
2517
+ write();
2362
2518
  }
2363
2519
  }
2364
2520
 
2365
- // src/lib/core/2.app/steps/apply-winner.ts
2366
- 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) {
2367
2526
  const demands = new Map(
2368
2527
  external.versions.map((v) => [v, versionDemands(v)])
2369
2528
  );
2370
2529
  return {
2371
- accepts: (version, tag) => demands.get(version).every((d) => isCompatible(tag, d.requiredVersion)),
2372
- 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))
2373
2532
  };
2374
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
+ }
2375
2543
  function createApplyWinner(config) {
2376
- return function applyWinner(externalName, external, winner, isCompatible, acceptance) {
2544
+ return function applyWinner(externalName, external, winner, acceptsTag, acceptance) {
2377
2545
  if (external.versions.length > 1) {
2378
- const { accepts, objector } = acceptance ?? versionAcceptance(external, isCompatible);
2379
2546
  const rebuilt = [];
2380
2547
  for (const v of external.versions) {
2381
- rebuilt.push(v);
2382
- if (accepts(v, winner.tag)) {
2383
- v.action = "skip";
2384
- continue;
2385
- }
2386
- const strict = objector(v, winner.tag);
2387
- if (config.strict.strictExternalCompatibility && strict) {
2388
- config.log.error(
2389
- 3,
2390
- `[${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}`
2391
2555
  );
2392
- throw new NFError(`External ${externalName}@${v.tag} could not be shared.`);
2393
- }
2394
- if (v === winner) continue;
2395
- if (!strict) {
2396
- v.action = "skip";
2397
- continue;
2398
- }
2399
- const objecting = new Set(
2400
- v.remotes.filter((r) => r.strictVersion && !isCompatible(winner.tag, r.requiredVersion))
2401
- );
2402
- if (objecting.size === v.remotes.length) {
2403
- v.action = "scope";
2404
- continue;
2405
- }
2406
- v.remotes = v.remotes.filter((r) => !objecting.has(r));
2407
- v.action = "skip";
2408
- rebuilt.push({ tag: v.tag, host: false, action: "scope", remotes: [...objecting] });
2556
+ rebuilt.push(...redirectRow(v, winner.tag, acceptsTag));
2409
2557
  }
2410
- const merged = /* @__PURE__ */ new Map();
2411
- external.versions = rebuilt.filter((v) => {
2412
- const first = merged.get(`${v.tag}|${v.action}`);
2413
- if (!first) {
2414
- merged.set(`${v.tag}|${v.action}`, v);
2415
- return true;
2416
- }
2417
- first.remotes.push(...v.remotes);
2418
- return false;
2419
- });
2558
+ external.versions = mergeRows(rebuilt, winner);
2420
2559
  }
2421
2560
  winner.action = "share";
2422
- applyEntrypointCoveragePolicy(externalName, external);
2423
2561
  external.dirty = false;
2424
2562
  return external;
2425
2563
  };
2426
- function applyEntrypointCoveragePolicy(externalName, external) {
2427
- const { strictEntryPointCoverage } = config.strict;
2428
- if (!strictEntryPointCoverage && !config.profile.scopeUncoveredEntrypoints) return;
2429
- const tears = findTears(external);
2430
- if (tears.length === 0) return;
2431
- if (strictEntryPointCoverage) {
2432
- const { version, remote, uncovered } = tears[0];
2433
- config.log.error(
2434
- 3,
2435
- `[${externalName}@${version.tag}][${remote.name}] Entrypoints not covered by the shared version: ${uncovered.join(", ")}.`
2436
- );
2437
- throw new NFError(
2438
- `External ${externalName} could not be shared without tearing entrypoints.`
2439
- );
2440
- }
2441
- scopeTornRemotes(externalName, external, tears);
2442
- }
2443
- function findTears(external) {
2444
- const shared = external.versions.find((v) => v.action === "share");
2445
- if (!shared) return [];
2446
- const basis = versionEntries(shared);
2447
- const tears = [];
2448
- for (const version of external.versions) {
2449
- if (version.action === "scope") continue;
2450
- if (version === shared) continue;
2451
- version.remotes.forEach((remote) => {
2452
- if (remote.servedBy) return;
2453
- const uncovered = uncoveredEntrypoints(remote, basis);
2454
- if (uncovered.length > 0) tears.push({ version, remote, uncovered });
2455
- });
2456
- }
2457
- return tears;
2458
- }
2459
- function scopeTornRemotes(externalName, external, tears) {
2460
- const torn = new Set(tears.map((t) => t.remote));
2461
- const demotedByTag = /* @__PURE__ */ new Map();
2462
- for (const { version, remote, uncovered } of tears) {
2463
- const group = demotedByTag.get(version.tag);
2464
- if (group) group.push(remote);
2465
- else demotedByTag.set(version.tag, [remote]);
2466
- config.log.debug(
2467
- 3,
2468
- `[${externalName}@${version.tag}][${remote.name}] Scoped: entrypoints not covered by the shared version: ${uncovered.join(", ")}.`
2469
- );
2470
- }
2471
- for (const version of external.versions) {
2472
- if (version.remotes.some((r) => torn.has(r))) {
2473
- version.remotes = version.remotes.filter((r) => !torn.has(r));
2474
- }
2475
- }
2476
- external.versions = external.versions.filter((v) => v.remotes.length > 0);
2477
- for (const [tag, remotes] of demotedByTag) {
2478
- const scoped = external.versions.find((v) => v.tag === tag && v.action === "scope");
2479
- if (scoped) scoped.remotes.push(...remotes);
2480
- else external.versions.push({ tag, host: false, action: "scope", remotes });
2481
- }
2482
- }
2483
2564
  }
2484
2565
 
2485
2566
  // src/lib/core/2.app/steps/determine-shared-externals.ts
2486
2567
  function createDetermineSharedExternals(config, ports) {
2487
2568
  const applyWinner = createApplyWinner(config);
2488
2569
  return () => {
2489
- const memo = /* @__PURE__ */ new Map();
2490
- const isCompatible = (tag, requiredVersion) => {
2491
- const key = `${tag}|${requiredVersion}`;
2492
- let hit = memo.get(key);
2493
- if (hit === void 0) {
2494
- hit = ports.versionCheck.isCompatible(tag, requiredVersion);
2495
- memo.set(key, hit);
2496
- }
2497
- return hit;
2498
- };
2499
- const touched = /* @__PURE__ */ new Map();
2570
+ const acceptsTag = createAcceptsTag(
2571
+ ports.versionCheck.isCompatible,
2572
+ ports.versionCheck.compare
2573
+ );
2500
2574
  for (const shareScope of ports.sharedExternalsRepo.getScopes()) {
2575
+ if (ports.sharedExternalsRepo.scopeType(shareScope) === "strict") continue;
2501
2576
  const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
2502
2577
  try {
2503
- const elected = /* @__PURE__ */ new Set();
2504
2578
  Object.entries(sharedExternals).filter(([_, e]) => e.dirty).forEach(([name, external]) => {
2505
2579
  ports.sharedExternalsRepo.addOrUpdate(
2506
2580
  name,
2507
- setVersionActions(name, external, isCompatible),
2581
+ setVersionActions(name, external, acceptsTag),
2508
2582
  shareScope
2509
2583
  );
2510
- elected.add(name);
2511
2584
  });
2512
- if (elected.size > 0) touched.set(shareScope, elected);
2513
2585
  } catch (error) {
2514
2586
  config.log.error(
2515
2587
  3,
@@ -2527,24 +2599,22 @@ function createDetermineSharedExternals(config, ports) {
2527
2599
  );
2528
2600
  }
2529
2601
  }
2530
- return Promise.resolve(touched);
2602
+ return Promise.resolve();
2531
2603
  };
2532
2604
  function uncoveredTears(external, winner, accepts) {
2533
- const basis = versionEntries(winner);
2605
+ const covered = /* @__PURE__ */ new Set();
2606
+ forEachVersionEntry(winner, void 0, (entrypoint) => covered.add(entrypoint));
2534
2607
  return external.versions.reduce((sum, v) => {
2535
2608
  if (v === winner) return sum;
2536
2609
  if (!accepts(v, winner.tag)) return sum;
2537
- return v.remotes.reduce(
2538
- (n, r) => r.servedBy ? n : n + countUncoveredEntrypoints(r, basis),
2539
- sum
2540
- );
2610
+ return v.remotes.reduce((n, r) => n + countUncoveredEntrypoints(r, covered), sum);
2541
2611
  }, 0);
2542
2612
  }
2543
- function setVersionActions(externalName, external, isCompatible) {
2613
+ function setVersionActions(externalName, external, acceptsTag) {
2614
+ const acceptance = versionAcceptance(external, acceptsTag);
2544
2615
  if (external.versions.length === 1) {
2545
- return applyWinner(externalName, external, external.versions[0], isCompatible);
2616
+ return applyWinner(externalName, external, external.versions[0], acceptsTag, acceptance);
2546
2617
  }
2547
- const acceptance = versionAcceptance(external, isCompatible);
2548
2618
  const { accepts } = acceptance;
2549
2619
  let sharedVersion = external.versions.find((v) => v.host);
2550
2620
  if (!sharedVersion && config.profile.latestSharedExternal) {
@@ -2569,14 +2639,12 @@ function createDetermineSharedExternals(config, ports) {
2569
2639
  return [v, [...groups.values()]];
2570
2640
  })
2571
2641
  );
2572
- 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
+ );
2573
2646
  external.versions.forEach((vA) => {
2574
- const extraDownloads = external.versions.reduce(
2575
- // A copy of the winner is never redirected, so it never self-serves however its own range
2576
- // reads — see `applyWinner`, which does not split the winner either.
2577
- (sum, vB) => vB === vA ? sum : sum + costOf(vB, vA.tag),
2578
- 0
2579
- );
2647
+ const extraDownloads = external.versions.reduce((sum, vB) => sum + costOf(vB, vA.tag), 0);
2580
2648
  if (extraDownloads < leastExtraDownloads) {
2581
2649
  leastExtraDownloads = extraDownloads;
2582
2650
  leastTears = uncoveredTears(external, vA, accepts);
@@ -2594,582 +2662,582 @@ function createDetermineSharedExternals(config, ports) {
2594
2662
  if (!sharedVersion) {
2595
2663
  throw new NFError(`[${externalName}] Could not determine shared version!`);
2596
2664
  }
2597
- return applyWinner(externalName, external, sharedVersion, isCompatible, acceptance);
2665
+ return applyWinner(externalName, external, sharedVersion, acceptsTag, acceptance);
2598
2666
  }
2599
2667
  }
2600
2668
 
2601
- // src/lib/core/2.app/steps/pooling/pool-views.ts
2602
- function liveBuilds(members, islanded) {
2603
- return walkBuilds(members, islanded, true);
2604
- }
2605
- function committedView(members) {
2606
- const global = /* @__PURE__ */ new Map();
2607
- forEachGlobalClaim(members, void 0, (specifier, tag, meta) => {
2608
- if (!global.has(specifier))
2609
- global.set(specifier, { tag, remote: meta.name, file: meta.entries[specifier] });
2610
- });
2611
- 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);
2612
2678
  }
2613
- function walkBuilds(members, islanded, skipScoped) {
2614
- const builds = /* @__PURE__ */ new Map();
2615
- for (const member of members) {
2616
- const versions = member.external.versions;
2617
- for (let v = 0; v < versions.length; v++) {
2618
- const version = versions[v];
2619
- if (skipScoped && version.action === "scope") continue;
2620
- const remotes = version.remotes;
2621
- for (let r = 0; r < remotes.length; r++) {
2622
- const meta = remotes[r];
2623
- if (islanded?.has(meta.name)) continue;
2624
- let own = builds.get(meta.name);
2625
- if (!own) {
2626
- builds.set(
2627
- meta.name,
2628
- own = { coverage: /* @__PURE__ */ new Map(), tags: /* @__PURE__ */ new Map(), instance: /* @__PURE__ */ new Map() }
2629
- );
2630
- }
2631
- if (!own.instance.has(member.name)) own.instance.set(member.name, version.tag);
2632
- for (const specifier in meta.entries) {
2633
- own.coverage.set(specifier, meta.entries[specifier]);
2634
- if (!own.tags.has(specifier)) own.tags.set(specifier, version.tag);
2635
- }
2636
- }
2637
- }
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);
2638
2699
  }
2639
- 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;
2640
2710
  }
2641
- function ownCopies(members, only) {
2642
- 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();
2643
2715
  for (const member of members) {
2644
- for (const version of member.external.versions) {
2716
+ const seen = /* @__PURE__ */ new Set();
2717
+ for (const version of member.external.versions)
2645
2718
  for (const meta of version.remotes) {
2646
- if (only && !only.has(meta.name)) continue;
2647
- let copies = own.get(meta.name);
2648
- if (!copies) own.set(meta.name, copies = []);
2649
- if (!copies.some((c) => c.member === member.name))
2650
- 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
+ });
2651
2731
  }
2652
- }
2653
2732
  }
2654
- return own;
2733
+ return shipped;
2655
2734
  }
2656
- function consumedMembers(members) {
2657
- const consumed = /* @__PURE__ */ new Map();
2658
- for (const member of members) {
2659
- const versions = member.external.versions;
2660
- for (let v = 0; v < versions.length; v++) {
2661
- const remotes = versions[v].remotes;
2662
- for (let r = 0; r < remotes.length; r++) {
2663
- const name = remotes[r].name;
2664
- const list = consumed.get(name);
2665
- if (!list) consumed.set(name, [member.name]);
2666
- else if (list[list.length - 1] !== member.name) list.push(member.name);
2667
- }
2668
- }
2669
- }
2670
- 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) };
2671
2745
  }
2672
- function consumedSpecifiers(members) {
2673
- const consumed = /* @__PURE__ */ new Map();
2674
- for (const member of members) {
2675
- const versions = member.external.versions;
2676
- for (let v = 0; v < versions.length; v++) {
2677
- const remotes = versions[v].remotes;
2678
- for (let r = 0; r < remotes.length; r++) {
2679
- const meta = remotes[r];
2680
- let own = consumed.get(meta.name);
2681
- if (!own) consumed.set(meta.name, own = /* @__PURE__ */ new Set());
2682
- for (const specifier in meta.entries) own.add(specifier);
2683
- }
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;
2684
2753
  }
2685
- }
2686
- return consumed;
2754
+ return true;
2687
2755
  }
2688
- function coversWholePool(members, build, islanded) {
2689
- const served = /* @__PURE__ */ new Set();
2690
- const wanted = /* @__PURE__ */ new Set();
2691
- for (const member of members) {
2692
- for (const version of member.external.versions) {
2693
- for (const meta of version.remotes) {
2694
- if (islanded.has(meta.name)) continue;
2695
- const into = meta.name === build ? served : wanted;
2696
- for (const specifier in meta.entries) into.add(specifier);
2697
- }
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;
2698
2761
  }
2699
- }
2700
- for (const specifier of wanted) if (!served.has(specifier)) return false;
2701
2762
  return true;
2702
2763
  }
2703
- function servingBuilds(members, islanded) {
2704
- const serving = /* @__PURE__ */ new Map();
2705
- for (const member of members) {
2706
- const shared = member.external.versions.find((v) => v.action === "share");
2707
- const basis = shared?.remotes.find((r) => !islanded.has(r.name));
2708
- if (basis) serving.set(member.name, basis.name);
2709
- }
2710
- 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];
2711
2773
  }
2712
- function basisPerMember(members, islanded, dedupsElsewhere) {
2713
- const basis = /* @__PURE__ */ new Map();
2714
- for (const member of members) {
2715
- const winner = member.external.versions.find((v) => v.action === "share");
2716
- if (!winner) continue;
2717
- const own = winner.remotes.find(
2718
- (r) => !islanded?.has(r.name) && !dedupsElsewhere?.(r.name)
2719
- );
2720
- if (own) basis.set(member.name, own.name);
2721
- }
2722
- 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
+ };
2723
2803
  }
2724
- function hostRemotes(members) {
2725
- const hosts = /* @__PURE__ */ new Set();
2726
- for (const member of members) {
2727
- for (const version of member.external.versions) {
2728
- 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;
2729
2820
  }
2730
- }
2731
- 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;
2732
2824
  }
2733
- function arrivalOrder(members) {
2734
- const arrival = /* @__PURE__ */ new Map();
2735
- for (const member of members) {
2736
- for (const version of member.external.versions) {
2737
- for (const meta of version.remotes) {
2738
- if (!arrival.has(meta.name)) arrival.set(meta.name, arrival.size);
2739
- }
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 });
2740
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));
2741
2870
  }
2742
- return arrival;
2743
2871
  }
2744
- function sharedTagPerSpecifier(members, islanded) {
2745
- const shared = /* @__PURE__ */ new Map();
2746
- forEachGlobalClaim(members, islanded, (specifier, tag) => {
2747
- 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;
2748
2887
  });
2749
- return shared;
2750
2888
  }
2751
- function forEachGlobalClaim(members, islanded, visit) {
2752
- const accepts = islanded ? (meta) => !islanded.has(meta.name) : void 0;
2753
- const claim = (version) => forEachVersionEntry(version, accepts, (specifier, meta) => visit(specifier, version.tag, meta));
2754
- for (const member of members) {
2755
- const versions = member.external.versions;
2756
- const winner = versions.find((v) => v.action === "share");
2757
- if (winner) claim(winner);
2758
- for (let v = 0; v < versions.length; v++) {
2759
- if (versions[v].action === "skip") claim(versions[v]);
2760
- }
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);
2761
2902
  }
2762
2903
  }
2763
-
2764
- // src/lib/core/2.app/steps/pooling/anchoring.ts
2765
- function acceptanceTable(members, isCompatible) {
2766
- const table = /* @__PURE__ */ new Map();
2767
- for (const member of members) {
2768
- const tags = [];
2769
- for (const version of member.external.versions) tags.push(version.tag);
2770
- for (const version of member.external.versions) {
2771
- for (const meta of version.remotes) {
2772
- let byMember = table.get(meta.name);
2773
- if (!byMember) table.set(meta.name, byMember = /* @__PURE__ */ new Map());
2774
- if (byMember.has(member.name)) continue;
2775
- const accepted = /* @__PURE__ */ new Set();
2776
- for (let t = 0; t < tags.length; t++) {
2777
- if (isCompatible(tags[t], meta.requiredVersion)) accepted.add(tags[t]);
2778
- }
2779
- byMember.set(member.name, accepted);
2780
- }
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);
2781
2913
  }
2782
- }
2783
- return table;
2914
+ return packages;
2784
2915
  }
2785
- function covers(coverage2, consumed) {
2786
- for (const specifier of consumed) if (!coverage2.has(specifier)) return false;
2787
- 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;
2788
2926
  }
2789
- function acceptsAll(acceptance, build, consumer, consumed) {
2790
- const byMember = acceptance.get(consumer);
2791
- if (!byMember) return false;
2792
- for (let i = 0; i < consumed.length; i++) {
2793
- const offered = build.get(consumed[i]);
2794
- if (offered === void 0) return false;
2795
- if (!byMember.get(consumed[i])?.has(offered)) return false;
2796
- }
2797
- 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;
2798
2937
  }
2799
- function isWitnessed(specifiers, shared, builds) {
2800
- const wanted = [...specifiers];
2801
- for (const specifier of wanted) if (!shared.has(specifier)) return false;
2802
- for (const [, build] of builds) {
2803
- let matches2 = true;
2804
- for (let i = 0; i < wanted.length; i++) {
2805
- if (build.tags.get(wanted[i]) !== shared.get(wanted[i])) {
2806
- matches2 = false;
2807
- 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;
2808
2958
  }
2809
- }
2810
- if (matches2) return true;
2811
2959
  }
2812
- return false;
2813
2960
  }
2814
- function explainSelfServe(remote, wants, specifiers, pool) {
2815
- let closest;
2816
- let fewest = Number.MAX_SAFE_INTEGER;
2817
- let gap = wants[0] ?? "";
2818
- for (const [build, view] of pool.builds) {
2819
- if (build === remote) continue;
2820
- let missing;
2821
- let count = 0;
2822
- for (const specifier of specifiers) {
2823
- if (view.coverage.has(specifier)) continue;
2824
- count++;
2825
- 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 };
2826
2979
  }
2827
- if (count >= fewest) continue;
2828
- fewest = count;
2829
- closest = build;
2830
- gap = missing ?? rejectedMember(view.instance, remote, wants, pool.acceptance) ?? gap;
2831
- }
2832
- 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.`);
2833
2987
  }
2834
- function rejectedMember(offered, consumer, wants, acceptance) {
2835
- const accepts = acceptance.get(consumer);
2836
- if (!accepts) return void 0;
2837
- for (const member of wants) {
2838
- const tag = offered.get(member);
2839
- if (tag === void 0) return member;
2840
- if (!accepts.get(member)?.has(tag)) return `${member}@${tag}`;
2841
- }
2842
- 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;
2843
2996
  }
2844
- function assignAnchors(input) {
2845
- const { builds, acceptance, consumedMembers: consumedMembers2, hosts, arrival } = input;
2846
- const assignment = /* @__PURE__ */ new Map();
2847
- const byPriority = (a, b) => (arrival.get(a) ?? Number.MAX_SAFE_INTEGER) - (arrival.get(b) ?? Number.MAX_SAFE_INTEGER) || a.localeCompare(b);
2848
- const candidates = [...builds.keys()].sort(byPriority);
2849
- const pending = new Set([...consumedMembers2.keys()].sort(byPriority));
2850
- const anchored = /* @__PURE__ */ new Set();
2851
- const canServe = (candidate, consumer) => {
2852
- const build = builds.get(candidate);
2853
- if (!covers(build.coverage, input.consumedSpecifiers.get(consumer) ?? [])) return false;
2854
- return acceptsAll(acceptance, build.instance, consumer, consumedMembers2.get(consumer) ?? []);
2855
- };
2856
- const electAnchor = (anchor, served) => {
2857
- pending.delete(anchor);
2858
- assignment.set(anchor, void 0);
2859
- for (const consumer of served) {
2860
- pending.delete(consumer);
2861
- anchored.add(consumer);
2862
- assignment.set(consumer, anchor);
2863
- }
2864
- };
2865
- for (const host of [...hosts].sort(byPriority)) {
2866
- const served = [];
2867
- if (builds.has(host)) {
2868
- for (const consumer of pending) {
2869
- if (consumer !== host && canServe(host, consumer)) served.push(consumer);
2870
- }
2871
- }
2872
- electAnchor(host, served);
2873
- }
2874
- for (; ; ) {
2875
- let best;
2876
- let bestServed = [];
2877
- for (const candidate of candidates) {
2878
- if (anchored.has(candidate)) continue;
2879
- const served = [];
2880
- for (const consumer of pending) {
2881
- if (consumer !== candidate && canServe(candidate, consumer)) served.push(consumer);
2882
- }
2883
- if (served.length > bestServed.length) {
2884
- best = candidate;
2885
- bestServed = served;
2886
- }
2887
- }
2888
- if (best === void 0) break;
2889
- electAnchor(best, bestServed);
2890
- }
2891
- for (const consumer of pending) assignment.set(consumer, void 0);
2892
- 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 };
2893
3008
  }
2894
3009
 
2895
- // src/lib/core/2.app/steps/pooling/pool-graph.ts
2896
- function createDSU() {
2897
- const ids = /* @__PURE__ */ new Map();
2898
- const parent = [];
2899
- const size = [];
2900
- const intern = (key) => {
2901
- let id = ids.get(key);
2902
- if (id === void 0) {
2903
- id = parent.length;
2904
- ids.set(key, id);
2905
- parent.push(id);
2906
- size.push(1);
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;
2907
3019
  }
2908
- return id;
3020
+ return node;
2909
3021
  };
2910
- const findRoot = (x) => {
2911
- while (parent[x] !== x) {
2912
- parent[x] = parent[parent[x]];
2913
- x = parent[x];
2914
- }
2915
- return x;
2916
- };
2917
- return {
2918
- union(a, b) {
2919
- let rootA = findRoot(intern(a));
2920
- let rootB = findRoot(intern(b));
2921
- if (rootA === rootB) return;
2922
- if (size[rootA] < size[rootB]) [rootA, rootB] = [rootB, rootA];
2923
- parent[rootB] = rootA;
2924
- size[rootA] = size[rootA] + size[rootB];
2925
- },
2926
- // Interns the key if unseen — an isolated key is its own component.
2927
- component(key) {
2928
- return findRoot(intern(key));
2929
- }
3022
+ const union = (a, b) => {
3023
+ const rootA = find(a);
3024
+ const rootB = find(b);
3025
+ if (rootA !== rootB) parent.set(rootB, rootA);
2930
3026
  };
3027
+ return { find, union };
2931
3028
  }
2932
3029
  var extNode = (name) => `ext\0${name}`;
2933
- var tagNode = (remote, tag) => `tag\0${remote}\0${tag}`;
2934
- function owningPackage(name) {
2935
- const depth = name.startsWith("@") ? 2 : 1;
2936
- let cut = -1;
2937
- for (let seen = 0; seen < depth; seen++) {
2938
- cut = name.indexOf("/", cut + 1);
2939
- if (cut === -1) return void 0;
2940
- }
2941
- return name.slice(0, cut);
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;
2942
3040
  }
2943
- function groupByMembership(candidates, log) {
2944
- const dsu = createDSU();
2945
- const tagged = /* @__PURE__ */ new Set();
2946
- for (const candidate of candidates) {
2947
- for (const edge of candidate.tags) {
2948
- dsu.union(extNode(candidate.name), tagNode(edge.remote, edge.tag));
2949
- tagged.add(candidate.name);
2950
- }
2951
- }
2952
- const declared = new Set(candidates.map((c) => c.name));
2953
- for (const candidate of candidates) {
2954
- const owner = owningPackage(candidate.name);
2955
- if (owner !== void 0 && declared.has(owner))
2956
- dsu.union(extNode(candidate.name), extNode(owner));
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));
2957
3048
  }
2958
3049
  const byComponent = /* @__PURE__ */ new Map();
2959
- for (const candidate of candidates) {
2960
- const root = dsu.component(extNode(candidate.name));
2961
- const members = byComponent.get(root) ?? byComponent.set(root, []).get(root);
2962
- members.push(candidate);
2963
- }
2964
- const pools = [];
2965
- for (const members of byComponent.values()) {
2966
- if (!members.some((m) => tagged.has(m.name))) continue;
2967
- members.sort((a, b) => a.name.localeCompare(b.name));
2968
- if (members.length < 2) {
2969
- const only = members[0];
2970
- if (tagged.has(only.name)) {
2971
- log?.warn(
2972
- 3,
2973
- `[${only.name}] declares a 'pool' tag but no other external joined its pool; likely a typo or a missing sibling.`
2974
- );
2975
- }
2976
- continue;
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));
3056
+ }
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
+ );
2977
3069
  }
2978
- pools.push(members);
2979
- }
2980
- pools.sort((a, b) => a[0].name.localeCompare(b[0].name));
2981
- const named = /* @__PURE__ */ new Map();
2982
- for (const members of pools) {
2983
- const label = mostDeclaredTag(members) ?? members[0].name;
2984
- let name = label;
2985
- for (let n = 2; named.has(name); n++) name = `${label}~${n}`;
2986
- named.set(
2987
- name,
2988
- members.map((m) => m.value)
2989
- );
2990
3070
  }
2991
- return named;
3071
+ return { pools, labelledAlone };
3072
+ }
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))
3082
+ );
2992
3083
  }
2993
- function mostDeclaredTag(members) {
3084
+ function mostDeclaredLabel(members, labelsOf) {
2994
3085
  const counts = /* @__PURE__ */ new Map();
2995
3086
  for (const member of members)
2996
- for (const edge of member.tags) counts.set(edge.tag, (counts.get(edge.tag) ?? 0) + 1);
2997
- let best;
2998
- for (const [tag, count] of counts) {
2999
- const top = best === void 0 ? 0 : counts.get(best);
3000
- if (count > top || count === top && tag.localeCompare(best) < 0) best = tag;
3001
- }
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];
3002
3093
  return best;
3003
3094
  }
3004
- function buildPools(sharedExternals, log) {
3005
- const candidates = Object.entries(sharedExternals).map(
3006
- ([name, external]) => ({
3007
- name,
3008
- tags: external.versions.flatMap(
3009
- (v) => v.remotes.flatMap((r) => {
3010
- const tag = r.pool?.trim();
3011
- return tag ? [{ remote: r.name, tag }] : [];
3012
- })
3013
- ),
3014
- value: { name, external }
3015
- })
3095
+
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)
3016
3101
  );
3017
- return groupByMembership(candidates, log);
3102
+ }
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;
3018
3111
  }
3019
3112
 
3020
- // src/lib/core/2.app/steps/pooling/pool.util.ts
3021
- function lazy(make) {
3022
- let value;
3023
- return () => value ??= make();
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;
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 };
3024
3148
  }
3025
- function remotesInPool(members) {
3026
- return [
3027
- ...new Set(members.flatMap((m) => m.external.versions.flatMap((v) => v.remotes.map((r) => r.name))))
3028
- ];
3149
+ function movedRecord(external, remote, move, compare) {
3150
+ return withoutStaleServingBuild(moveCopy(external, remote, move, compare));
3029
3151
  }
3030
- function poolableScopes(repo) {
3031
- return repo.getScopes().filter((scope) => repo.scopeType(scope) !== "strict" && repo.hasPoolState(scope));
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;
3032
3159
  }
3033
- function syncPoolNames(sharedExternals, pools, repo, scope, skip = /* @__PURE__ */ new Set()) {
3034
- const named = /* @__PURE__ */ new Map();
3035
- for (const [name, members] of pools) for (const member of members) named.set(member.name, name);
3036
- for (const [name, external] of Object.entries(sharedExternals)) {
3037
- const pool = named.get(name);
3038
- if (pool !== void 0 && skip.has(pool)) continue;
3039
- if (external.poolName === pool) continue;
3040
- const { poolName: _stale, ...rest } = external;
3041
- repo.addOrUpdate(
3042
- name,
3043
- pool !== void 0 ? { ...rest, poolName: pool } : {
3044
- ...rest,
3045
- versions: external.versions.map((v) => ({
3046
- ...v,
3047
- remotes: v.remotes.map(({ poolCause: _cause, ...meta }) => meta)
3048
- }))
3049
- },
3050
- scope
3051
- );
3052
- }
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
-
3055
- // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
3056
- function islandedRemotes(members) {
3057
- const islanded = /* @__PURE__ */ new Map();
3058
- for (const member of members)
3059
- for (const version of member.external.versions)
3060
- if (version.action === "scope") {
3061
- for (const remote of version.remotes)
3062
- if (!islanded.has(remote.name))
3063
- islanded.set(remote.name, {
3064
- kind: "incompatible",
3065
- member: member.name,
3066
- tag: version.tag
3067
- });
3068
- }
3069
- return islanded;
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 };
3070
3195
  }
3071
- var basisFor = (members, islanded, serving) => basisPerMember(members, islanded, (remote) => {
3072
- const build = serving.get(remote);
3073
- return build !== void 0 && build !== remote;
3074
- });
3075
- function servedPerRemote(consumed, serving, basis) {
3076
- const served = /* @__PURE__ */ new Map();
3077
- for (const [remote, build] of serving) {
3078
- const byMember = /* @__PURE__ */ new Map();
3079
- for (const name of consumed.get(remote) ?? []) {
3080
- if (basis.get(name) !== build) byMember.set(name, build);
3081
- }
3082
- if (byMember.size > 0) served.set(remote, byMember);
3083
- }
3084
- return served;
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
+ }))
3204
+ };
3085
3205
  }
3086
- var anyVerdictStored = (members) => members.some(
3087
- (m) => m.external.versions.some(
3088
- (v) => v.remotes.some((r) => r.servedBy !== void 0 || r.poolCause !== void 0)
3089
- )
3090
- );
3091
- function findTornRemotes(members, islanded, basis, served, hosts) {
3092
- const { suspect, selfServed } = movedRemotes(members, islanded, basis, served, hosts);
3093
- if (suspect.size === 0) return [];
3094
- const builds = liveBuilds(members, islanded);
3095
- const shared = sharedTagPerSpecifier(members, islanded);
3096
- const own = ownCopies(members, suspect);
3097
- const torn = [];
3098
- for (const remote of suspect) {
3099
- const copies = own.get(remote) ?? [];
3100
- const mine = selfServed.get(remote);
3101
- const anchors = served.get(remote);
3102
- const ownTags = /* @__PURE__ */ new Map();
3103
- const resolved = /* @__PURE__ */ new Map();
3104
- for (const copy of copies) {
3105
- const from = mine?.has(copy.member) ? remote : anchors?.get(copy.member);
3106
- for (const specifier in copy.entries) {
3107
- if (!ownTags.has(specifier)) ownTags.set(specifier, copy.tag);
3108
- const tag = from === remote ? copy.tag : from !== void 0 ? builds.get(from)?.tags.get(specifier) : shared.get(specifier);
3109
- if (tag !== void 0 && !resolved.has(specifier)) resolved.set(specifier, tag);
3110
- }
3111
- }
3112
- if (resolved.size === 0) continue;
3113
- if (ships(ownTags, resolved)) continue;
3114
- let witnessed = false;
3115
- for (const [, build] of builds) {
3116
- if (ships(build.tags, resolved)) {
3117
- witnessed = true;
3118
- break;
3119
- }
3120
- }
3121
- if (!witnessed) {
3122
- torn.push({
3123
- remote,
3124
- combination: [...resolved].map(([specifier, tag]) => `${specifier}@${tag}`).join(", ")
3125
- });
3126
- }
3127
- }
3128
- return torn;
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;
3129
3212
  }
3130
- var ships = (build, resolved) => {
3131
- if (!build) return false;
3132
- for (const [specifier, tag] of resolved) if (build.get(specifier) !== tag) return false;
3133
- return true;
3134
- };
3135
- function movedRemotes(members, islanded, basis, served, hosts) {
3136
- const suspect = /* @__PURE__ */ new Set();
3137
- const selfServed = /* @__PURE__ */ new Map();
3138
- const add = (remote) => {
3139
- if (!islanded.has(remote) && !hosts.has(remote)) suspect.add(remote);
3140
- };
3141
- for (const remote of served.keys()) add(remote);
3142
- for (const member of members) {
3143
- const unpublished = !basis.has(member.name);
3144
- for (const version of member.external.versions) {
3145
- const scoped = version.action === "scope";
3146
- if (!unpublished && !scoped) continue;
3147
- for (const meta of version.remotes) {
3148
- add(meta.name);
3149
- const servesItself = scoped || unpublished && served.get(meta.name)?.get(member.name) === void 0;
3150
- if (!servesItself) continue;
3151
- let mine = selfServed.get(meta.name);
3152
- if (!mine) selfServed.set(meta.name, mine = /* @__PURE__ */ new Set());
3153
- mine.add(member.name);
3154
- }
3155
- }
3156
- }
3157
- return { suspect, selfServed };
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 };
3158
3225
  }
3226
+
3227
+ // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
3159
3228
  function createPoolSharedExternals(config, ports) {
3160
- return (touched) => {
3161
- for (const scope of poolableScopes(ports.sharedExternalsRepo)) {
3162
- const touchedInScope = touched?.get(scope);
3163
- if (touched && !touchedInScope) continue;
3164
- 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);
3165
3234
  try {
3166
- const pools = buildPools(sharedExternals, config.log);
3167
- const rebuilt = /* @__PURE__ */ new Set();
3168
- for (const [poolName, members] of pools) {
3169
- if (touchedInScope && !members.some((m) => touchedInScope.has(m.name))) continue;
3170
- if (poolFamily(poolName, members, scope)) rebuilt.add(poolName);
3171
- }
3172
- syncPoolNames(sharedExternals, pools, ports.sharedExternalsRepo, scope, rebuilt);
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);
3173
3241
  } catch (error) {
3174
3242
  if (error instanceof NFError) return Promise.reject(error);
3175
3243
  config.log.error(3, `[${scope}] failed to pool shared externals.`, {
@@ -3183,253 +3251,439 @@ function createPoolSharedExternals(config, ports) {
3183
3251
  }
3184
3252
  return Promise.resolve();
3185
3253
  };
3186
- function poolFamily(poolName, members, scope) {
3187
- if (members.length < 2) return false;
3188
- const allRemotes = remotesInPool(members);
3189
- if (allRemotes.length < 2) {
3190
- if (!anyVerdictStored(members)) return false;
3191
- const basis2 = basisFor(members, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map());
3192
- for (const member of members)
3193
- ports.sharedExternalsRepo.addOrUpdate(
3194
- member.name,
3195
- rebuildMember(poolName, member, /* @__PURE__ */ new Map(), /* @__PURE__ */ new Map(), basis2.get(member.name)),
3196
- scope
3197
- );
3198
- return true;
3199
- }
3200
- const islanded = islandedRemotes(members);
3201
- const consumed = consumedMembers(members);
3202
- const hosts = hostRemotes(members);
3203
- const views = {
3204
- consumedSpecifiers: lazy(() => consumedSpecifiers(members)),
3205
- acceptance: lazy(() => acceptanceTable(members, ports.versionCheck.isCompatible)),
3206
- arrival: lazy(() => arrivalOrder(members))
3207
- };
3208
- config.log.debug(
3209
- 3,
3210
- `[${scope}][pool:${poolName}] ${members.length} members across ${allRemotes.length} remotes, incompatible={${[...islanded.keys()].join(", ") || "\u2205"}}
3211
- ` + members.map((m) => ` - ${m.name}`).join("\n")
3212
- );
3213
- const assign = () => {
3214
- const serving2 = assignServingBuilds(members, islanded, consumed, hosts, views);
3215
- const basis2 = basisFor(members, islanded, serving2);
3216
- return { serving: serving2, basis: basis2, served: servedPerRemote(consumed, serving2, basis2) };
3217
- };
3218
- let { serving, basis, served } = assign();
3219
- if (islanded.size === 0 && serving.size === 0 && !anyVerdictStored(members)) return false;
3220
- for (; ; ) {
3221
- const torn = findTornRemotes(members, islanded, basis, served, hosts);
3222
- if (torn.length === 0) break;
3223
- for (const { remote, combination } of torn)
3224
- islanded.set(remote, { kind: "torn", combination });
3225
- ({ 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
+ );
3226
3261
  }
3227
- const incompatible = [...islanded].filter(([, cause]) => cause.kind === "incompatible").map(([remote]) => remote);
3228
- 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) {
3229
3292
  config.log.error(
3230
3293
  3,
3231
- `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${incompatible.join(", ")}}.`
3294
+ `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${rejecting.join(", ")}}.`
3232
3295
  );
3233
3296
  throw new NFError(`Could not pool '${poolName}' in scope ${scope}.`);
3234
3297
  }
3235
- for (const [remote, cause] of islanded) {
3236
- const scoped = consumed.get(remote)?.length ?? 0;
3237
- config.log.warn(3, `[${scope}][pool:${poolName}] ${islandWarning(remote, cause, scoped)}`);
3238
- }
3239
- for (const member of members) {
3240
- const rebuilt = rebuildMember(poolName, member, islanded, served, basis.get(member.name));
3241
- warnIfScopedOnly(poolName, member, rebuilt, islanded, scope);
3242
- ports.sharedExternalsRepo.addOrUpdate(member.name, rebuilt, scope);
3243
- }
3244
- return true;
3245
- }
3246
- function assignServingBuilds(members, islanded, consumed, hosts, views) {
3247
- for (; ; ) {
3248
- const bases = servingBuilds(members, islanded);
3249
- const distinct = new Set(bases.values());
3250
- if (distinct.size === 1 && bases.size === members.length) {
3251
- const sole = [...distinct][0];
3252
- if (coversWholePool(members, sole, islanded)) return /* @__PURE__ */ new Map();
3253
- }
3254
- const builds = liveBuilds(members, islanded);
3255
- const shared = sharedTagPerSpecifier(members, islanded);
3256
- const consumedSpec = views.consumedSpecifiers();
3257
- const needAnchor = /* @__PURE__ */ new Map();
3258
- const needSpecifiers = /* @__PURE__ */ new Map();
3259
- for (const [remote, wants] of consumed) {
3260
- if (islanded.has(remote)) continue;
3261
- if (isWitnessed(consumedSpec.get(remote) ?? [], shared, builds)) continue;
3262
- needAnchor.set(remote, wants);
3263
- needSpecifiers.set(remote, consumedSpec.get(remote) ?? /* @__PURE__ */ new Set());
3264
- }
3265
- if (needAnchor.size === 0) return /* @__PURE__ */ new Map();
3266
- const acceptance = views.acceptance();
3267
- const assignment = assignAnchors({
3268
- builds,
3269
- acceptance,
3270
- consumedSpecifiers: needSpecifiers,
3271
- consumedMembers: needAnchor,
3272
- hosts,
3273
- arrival: views.arrival()
3274
- });
3275
- const anchors = /* @__PURE__ */ new Set();
3276
- for (const build of assignment.values()) if (build !== void 0) anchors.add(build);
3277
- const serving = /* @__PURE__ */ new Map();
3278
- let scoped = false;
3279
- for (const [remote, wants] of needAnchor) {
3280
- const assigned = assignment.get(remote);
3281
- const build = assigned ?? (anchors.has(remote) || hosts.has(remote) ? remote : void 0);
3282
- if (build === void 0) {
3283
- islanded.set(remote, {
3284
- kind: "uncovered",
3285
- ...explainSelfServe(remote, wants, needSpecifiers.get(remote), { builds, acceptance })
3286
- });
3287
- scoped = true;
3288
- continue;
3289
- }
3290
- 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;
3291
3310
  }
3292
- 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
+ );
3293
3320
  }
3294
3321
  }
3295
- function warnIfScopedOnly(poolName, member, rebuilt, islanded, scope) {
3296
- if (rebuilt.versions.some((v) => v.action === "share")) return;
3297
- const consumers = new Set(
3298
- rebuilt.versions.filter((v) => v.action === "scope").flatMap((v) => v.remotes.map((r) => r.name))
3299
- ).size;
3300
- if (consumers < 2) return;
3301
- const provider = member.external.versions.find((v) => v.action === "share");
3302
- if (provider?.remotes.some((r) => islanded.has(r.name))) return;
3303
- config.log.warn(
3304
- 3,
3305
- `[${scope}][pool:${poolName}] '${member.name}' is scoped-only \u2014 no coherent shared build provides it; ${consumers} remotes download their own copy.`
3306
- );
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;
3307
3364
  }
3308
- function rebuildMember(poolName, member, islanded, served, basis) {
3309
- let scoped = [];
3310
- let clean = [];
3311
- for (const version of member.external.versions) {
3312
- for (const meta of version.remotes) {
3313
- const entry = {
3314
- remote: meta.name,
3315
- tag: version.tag,
3316
- host: version.host,
3317
- action: version.action,
3318
- meta
3319
- };
3320
- (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] });
3321
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);
3322
3411
  }
3323
- for (const entry of scoped) delete entry.meta.servedBy;
3324
- for (const entry of clean) {
3325
- const build = served.get(entry.remote)?.get(member.name);
3326
- if (build !== void 0) entry.meta.servedBy = build;
3327
- else delete entry.meta.servedBy;
3328
- delete entry.meta.poolCause;
3329
- }
3330
- if (basis === void 0) {
3331
- scoped = [...scoped, ...clean.filter((e) => e.meta.servedBy === void 0)];
3332
- 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
+ });
3333
3431
  }
3334
- for (const entry of scoped)
3335
- entry.meta.poolCause = islanded.get(entry.remote)?.kind ?? "unshared";
3336
- const shareEntries = clean.filter((e) => e.action === "share").sort((a, b) => Number(b.remote === basis) - Number(a.remote === basis));
3337
- const shareVersion = shareEntries.length > 0 ? [
3338
- {
3339
- tag: shareEntries[0].tag,
3340
- host: shareEntries[0].host,
3341
- action: "share",
3342
- remotes: shareEntries.map((e) => e.meta)
3343
- }
3344
- ] : [];
3345
- const byTag = (entries, action) => {
3346
- const versions = /* @__PURE__ */ new Map();
3347
- for (const entry of entries) {
3348
- const version = versions.get(entry.tag) ?? {
3349
- tag: entry.tag,
3350
- host: entry.host,
3351
- action,
3352
- remotes: []
3353
- };
3354
- version.remotes.push(entry.meta);
3355
- 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 }]);
3356
3455
  }
3357
- return [...versions.values()];
3358
- };
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));
3359
3480
  return {
3360
- dirty: false,
3361
- poolName,
3362
- versions: [
3363
- ...shareVersion,
3364
- ...byTag(
3365
- clean.filter((e) => e.action === "skip"),
3366
- "skip"
3367
- ),
3368
- ...byTag(scoped, "scope")
3369
- ].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)
3370
3483
  };
3371
- }
3484
+ };
3372
3485
  }
3373
- function islandWarning(remote, cause, scoped) {
3374
- switch (cause.kind) {
3375
- case "incompatible":
3376
- return `'${remote}' is islanded: the resolver scoped its '${cause.member}@${cause.tag}', so all ${scoped} members it imports are scoped for it.`;
3377
- case "torn":
3378
- 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.`;
3379
- default: {
3380
- const closest = cause.closest ? `closest is '${cause.closest}'` : "no other build in the pool serves any of it";
3381
- 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.`;
3382
- }
3383
- }
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;
3384
3495
  }
3385
-
3386
- // src/lib/core/2.app/steps/pooling/mark-pools-for-reelection.ts
3387
- function clearPoolState(external) {
3388
- let cleared = external.poolName !== void 0;
3389
- delete external.poolName;
3390
- for (const version of external.versions)
3391
- for (const meta of version.remotes) {
3392
- if (meta.servedBy !== void 0 || meta.poolCause !== void 0) cleared = true;
3393
- delete meta.servedBy;
3394
- delete meta.poolCause;
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);
3503
+ }
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);
3395
3530
  }
3396
- return cleared;
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));
3397
3556
  }
3398
- function createMarkPoolsForReelection(config, ports) {
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));
3564
+ }
3565
+
3566
+ // src/lib/core/2.app/steps/decide-serving-builds.ts
3567
+ function createDecideServingBuilds(config, ports) {
3399
3568
  return () => {
3400
- for (const scope of poolableScopes(ports.sharedExternalsRepo)) {
3401
- const sharedExternals = ports.sharedExternalsRepo.getFromScope(scope);
3402
- if (!Object.values(sharedExternals).some((external) => external.dirty)) continue;
3403
- let spread = 0;
3404
- let unpooled = 0;
3405
- const pooled = /* @__PURE__ */ new Set();
3406
- for (const [, members] of buildPools(sharedExternals)) {
3407
- for (const member of members) pooled.add(member.name);
3408
- if (!members.some((m) => m.external.dirty)) continue;
3409
- for (const member of members)
3410
- if (!member.external.dirty) {
3411
- member.external.dirty = true;
3412
- spread++;
3413
- }
3414
- }
3415
- for (const [name, external] of Object.entries(sharedExternals)) {
3416
- if (pooled.has(name) || !clearPoolState(external)) continue;
3417
- external.dirty = true;
3418
- unpooled++;
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);
3419
3582
  }
3420
- if (spread > 0)
3421
- config.log.debug(3, `[${scope}] ${spread} pool member(s) marked dirty for re-election.`);
3422
- if (unpooled > 0)
3423
- config.log.debug(
3424
- 3,
3425
- `[${scope}] ${unpooled} external(s) left every pool; cleared their pool state for re-election.`
3583
+ return Promise.resolve();
3584
+ } catch (e) {
3585
+ return Promise.reject(e);
3586
+ }
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}`
3426
3619
  );
3427
3620
  }
3428
- return Promise.resolve();
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);
3429
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;
3430
3684
  }
3431
3685
 
3432
- // node_modules/.pnpm/@softarc+native-federation@4.7.0/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
3433
3687
  var CHUNK_PREFIX = "@nf-internal";
3434
3688
  function toChunkImport(fileName) {
3435
3689
  if (fileName.startsWith("./")) {
@@ -3446,8 +3700,8 @@ function createGenerateImportMap(config, ports) {
3446
3700
  const chunkBundles = {};
3447
3701
  try {
3448
3702
  addScopedExternals(importMap, chunkBundles);
3449
- addshareScopeExternals(importMap, chunkBundles);
3450
- addGlobalSharedExternals(importMap, chunkBundles);
3703
+ addNamedShareScopes(importMap, chunkBundles);
3704
+ addGlobalShareScope(importMap, chunkBundles);
3451
3705
  addRemoteInfos(importMap, chunkBundles);
3452
3706
  addChunkImports(importMap, chunkBundles);
3453
3707
  return Promise.resolve(importMap);
@@ -3481,47 +3735,61 @@ function createGenerateImportMap(config, ports) {
3481
3735
  }
3482
3736
  return modules;
3483
3737
  }
3484
- function addshareScopeExternals(importMap, chunkBundles) {
3738
+ function addNamedShareScopes(importMap, chunkBundles) {
3485
3739
  const shareScopes = ports.sharedExternalsRepo.getScopes({ includeGlobal: false });
3486
3740
  for (const shareScope of shareScopes) {
3487
- 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
+ }
3488
3753
  }
3489
3754
  return importMap;
3490
3755
  }
3491
- function processshareScope(importMap, shareScope, chunkBundles) {
3492
- const sharedExternals = ports.sharedExternalsRepo.getFromScope(shareScope);
3756
+ function addShareScope(importMap, shareScope, sharedExternals, chunkBundles) {
3493
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
+ };
3494
3763
  for (const [externalName, external] of Object.entries(sharedExternals)) {
3495
3764
  let override = void 0;
3496
- const cachedBefore = cachedFingerprint(external);
3497
3765
  for (const version of external.versions) {
3498
3766
  if (version.action === "scope") {
3499
- for (const remote of version.remotes) {
3500
- const remoteScope = getScope2(shareScope, remote.name, externalName);
3501
- for (const [packageName, file] of Object.entries(remote.entries)) {
3502
- const url = join(remoteScope, file);
3503
- addToScope(importMap, remoteScope, { [packageName]: url });
3504
- addIntegrity(importMap, url, remote.name, file);
3505
- }
3506
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3507
- remote.cached = true;
3508
- }
3767
+ addScopeRow(importMap, chunkBundles, shareScope, externalName, version);
3509
3768
  continue;
3510
3769
  }
3511
- version.remotes[0].cached = true;
3512
3770
  let serving = version;
3513
3771
  if (version.action === "skip") {
3514
3772
  if (!override) {
3515
- 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";
3516
3779
  }
3517
3780
  if (override !== "NOT_AVAILABLE") {
3518
3781
  serving = override;
3519
3782
  version.remotes[0].cached = false;
3520
3783
  }
3521
3784
  }
3522
- const provided = versionEntries(serving);
3785
+ const provided = versionClaims(serving, external.servingBuild);
3523
3786
  const mappings = [];
3524
3787
  for (const [packageName, provider] of provided) {
3788
+ const claim = claimedElsewhere(externalName, packageName);
3789
+ if (claim) {
3790
+ mappings.push({ packageName, ...claim });
3791
+ continue;
3792
+ }
3525
3793
  const file = provider.entries[packageName];
3526
3794
  mappings.push({
3527
3795
  packageName,
@@ -3534,56 +3802,60 @@ function createGenerateImportMap(config, ports) {
3534
3802
  registerBundleChunks(chunkBundles, provider.name, provider.bundle);
3535
3803
  }
3536
3804
  }
3537
- version.remotes.forEach((r) => {
3538
- const rScope = getScope2(shareScope, r.name, externalName);
3539
- if (index && r.servedBy) {
3540
- const files = index.get(r.servedBy);
3541
- const servedScope = getScope2(shareScope, r.servedBy, externalName);
3542
- 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)) {
3543
3811
  const source = files?.get(specifier);
3544
3812
  if (!source) continue;
3545
3813
  const url = join(servedScope, source.file);
3546
- addToScope(importMap, rScope, { [specifier]: url });
3547
- addIntegrity(importMap, url, r.servedBy, source.file);
3548
- 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);
3549
3817
  source.meta.cached = true;
3550
3818
  }
3551
3819
  return;
3552
3820
  }
3553
3821
  for (const m of mappings) {
3554
- addToScope(importMap, rScope, { [m.packageName]: m.url });
3822
+ addToScope(importMap, consumerScope, { [m.packageName]: m.url });
3555
3823
  }
3556
- for (const [packageName, file] of Object.entries(r.entries)) {
3824
+ for (const packageName of Object.keys(consumer.entries)) {
3557
3825
  if (provided.has(packageName)) continue;
3558
- if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
3559
- warnUncoveredEntrypoint(shareScope, externalName, r.name, packageName);
3560
- continue;
3561
- }
3562
- const url = join(rScope, file);
3563
- addToScope(importMap, rScope, { [packageName]: url });
3564
- addIntegrity(importMap, url, r.name, file);
3565
- r.cached = true;
3826
+ const claim = claimedElsewhere(externalName, packageName);
3827
+ if (claim) addToScope(importMap, consumerScope, { [packageName]: claim.url });
3566
3828
  }
3567
3829
  });
3568
3830
  for (const m of mappings) {
3569
3831
  addIntegrity(importMap, m.url, m.name, m.file);
3570
3832
  }
3571
3833
  }
3572
- if (cachedFingerprint(external) !== cachedBefore) {
3573
- 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);
3574
3843
  }
3844
+ registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3845
+ remote.cached = true;
3575
3846
  }
3576
3847
  }
3577
3848
  function cachedFingerprint(external) {
3578
3849
  return external.versions.map((v) => v.remotes.map((r) => r.cached ? 1 : 0).join("")).join("|");
3579
3850
  }
3580
- function findOverride(external, shareScope, externalName) {
3851
+ function findOverride(external, shareScope, externalName, servedElsewhere) {
3581
3852
  const sharedVersions = external.versions.filter((v) => v.action === "share");
3582
3853
  const scopedExternalName = `${shareScope}.${externalName}`;
3583
3854
  if (sharedVersions.length > 1) {
3584
3855
  handleMultipleSharedVersions(scopedExternalName);
3585
3856
  }
3586
- 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()) {
3587
3859
  if (config.strict.strictImportMap) {
3588
3860
  config.log.error(4, `[${shareScope}][${externalName}] shareScope has no override version.`);
3589
3861
  throw new NFError("Could not create ImportMap.");
@@ -3628,17 +3900,30 @@ function createGenerateImportMap(config, ports) {
3628
3900
  }
3629
3901
  return index;
3630
3902
  }
3631
- 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) {
3632
3917
  for (const consumer of version.remotes) {
3633
3918
  if (!consumer.servedBy) continue;
3634
3919
  const files = index.get(consumer.servedBy);
3635
- const scope = getScope2(ctx, consumer.name, externalName);
3920
+ const scope = getScope2(shareScope, consumer.name, externalName);
3636
3921
  for (const specifier of Object.keys(consumer.entries)) {
3637
3922
  const source = files?.get(specifier);
3638
3923
  if (!source) {
3639
3924
  config.log.warn(
3640
3925
  4,
3641
- `[${ctx}][${externalName}][${consumer.name}] '${consumer.servedBy}' does not serve '${specifier}'.`
3926
+ `[${shareScope}][${externalName}][${consumer.name}] '${consumer.servedBy}' does not serve '${specifier}'.`
3642
3927
  );
3643
3928
  continue;
3644
3929
  }
@@ -3656,9 +3941,11 @@ function createGenerateImportMap(config, ports) {
3656
3941
  source.meta.cached = true;
3657
3942
  }
3658
3943
  }
3659
- function addGlobalSharedExternals(importMap, chunkBundles) {
3944
+ function addGlobalShareScope(importMap, chunkBundles) {
3660
3945
  const sharedExternals = ports.sharedExternalsRepo.getFromScope();
3661
3946
  const index = buildServedIndex(sharedExternals);
3947
+ const { claims, duplicates } = claimTable(Object.entries(sharedExternals));
3948
+ const claimed = claimsByCopy(claims);
3662
3949
  const served = [];
3663
3950
  const cachedBefore = /* @__PURE__ */ new Map();
3664
3951
  for (const [externalName, external] of Object.entries(sharedExternals)) {
@@ -3669,27 +3956,14 @@ function createGenerateImportMap(config, ports) {
3669
3956
  }
3670
3957
  if (version.action === "skip") continue;
3671
3958
  if (version.action === "scope") {
3672
- for (const remote of version.remotes) {
3673
- const remoteScope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
3674
- for (const [packageName, file] of Object.entries(remote.entries)) {
3675
- const url = join(remoteScope, file);
3676
- addToScope(importMap, remoteScope, { [packageName]: url });
3677
- addIntegrity(importMap, url, remote.name, file);
3678
- }
3679
- remote.cached = true;
3680
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3681
- }
3959
+ addScopeRow(importMap, chunkBundles, GLOBAL_SCOPE, externalName, version);
3682
3960
  continue;
3683
3961
  }
3684
- if (importMap.imports[externalName]) {
3962
+ if (duplicates.has(version)) {
3685
3963
  notifyDuplicateGlobalExternal(externalName);
3686
3964
  continue;
3687
3965
  }
3688
- mergeVersionEntries(importMap, chunkBundles, externalName, version);
3689
- }
3690
- for (const version of external.versions) {
3691
- if (version.action !== "skip") continue;
3692
- selfFillUncovered(importMap, chunkBundles, externalName, version.remotes);
3966
+ publishClaims(importMap, chunkBundles, externalName, version, claimed);
3693
3967
  }
3694
3968
  }
3695
3969
  flushServed(importMap, chunkBundles, served);
@@ -3700,54 +3974,30 @@ function createGenerateImportMap(config, ports) {
3700
3974
  }
3701
3975
  return importMap;
3702
3976
  }
3703
- function mergeVersionEntries(importMap, chunkBundles, externalName, version) {
3704
- const scopes = /* @__PURE__ */ new Map();
3705
- for (const [packageName, remote] of versionEntries(version)) {
3706
- if (importMap.imports[packageName]) {
3707
- config.log.debug(
3708
- 4,
3709
- `[${GLOBAL_SCOPE}][${externalName}][${remote.name}] Entrypoint '${packageName}' is already mapped by another external, keeping '${importMap.imports[packageName]}'.`
3710
- );
3711
- continue;
3712
- }
3713
- let scope = scopes.get(remote.name);
3714
- if (scope === void 0) {
3715
- scopes.set(remote.name, scope = getScope2(GLOBAL_SCOPE, remote.name, externalName));
3716
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3717
- remote.cached = true;
3718
- }
3719
- const file = remote.entries[packageName];
3720
- const url = join(scope, file);
3721
- addToGlobal(importMap, { [packageName]: url });
3722
- 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]);
3723
3983
  }
3724
- }
3725
- function selfFillUncovered(importMap, chunkBundles, externalName, remotes) {
3726
- for (const remote of remotes) {
3727
- if (remote.servedBy) continue;
3728
- for (const [packageName, file] of Object.entries(remote.entries)) {
3729
- if (importMap.imports[packageName]) continue;
3730
- if (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints) {
3731
- warnUncoveredEntrypoint(GLOBAL_SCOPE, externalName, remote.name, packageName);
3732
- continue;
3733
- }
3734
- 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];
3735
3995
  const url = join(scope, file);
3736
- addToGlobal(importMap, { [packageName]: url });
3737
- addIntegrity(importMap, url, remote.name, file);
3738
- registerBundleChunks(chunkBundles, remote.name, remote.bundle);
3739
- remote.cached = true;
3996
+ addToGlobal(importMap, { [specifier]: url });
3997
+ addIntegrity(importMap, url, copy.name, file);
3740
3998
  }
3741
3999
  }
3742
4000
  }
3743
- function warnUncoveredEntrypoint(scope, externalName, remoteName, packageName) {
3744
- const msg = `[${scope}][${externalName}][${remoteName}] Entrypoint '${packageName}' is not covered by the shared version.`;
3745
- if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
3746
- config.log.error(4, msg);
3747
- throw new NFError("Could not create ImportMap.");
3748
- }
3749
- config.log.warn(4, msg);
3750
- }
3751
4001
  function notifyDuplicateGlobalExternal(externalName) {
3752
4002
  if (config.strict.strictImportMap) {
3753
4003
  config.log.error(4, `[${externalName}] Shared external has multiple shared versions.`);
@@ -3821,15 +4071,30 @@ function createGenerateImportMap(config, ports) {
3821
4071
  });
3822
4072
  }
3823
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
+ }
3824
4081
 
3825
4082
  // src/lib/core/2.app/steps/commit-changes.ts
3826
4083
  function createCommitChanges(config, ports) {
3827
- 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
+ });
3828
4088
  function addToBrowser(importMap, opts) {
3829
4089
  ports.browser.setImportMapFn(importMap, opts);
3830
4090
  config.log.debug(5, "Added import map to browser.", importMap);
3831
4091
  return importMap;
3832
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
+ }
3833
4098
  function persistRepositoryChanges() {
3834
4099
  ports.remoteInfoRepo.commit();
3835
4100
  ports.scopedExternalsRepo.commit();
@@ -3839,6 +4104,29 @@ function createCommitChanges(config, ports) {
3839
4104
  }
3840
4105
  }
3841
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
+
3842
4130
  // src/lib/core/2.app/steps/expose-module-loader.ts
3843
4131
  function createExposeModuleLoader(config, ports) {
3844
4132
  const loadRemoteModule = (remoteName, exposedModule) => {
@@ -3874,10 +4162,16 @@ var normalizeRemoteRef = (remote) => {
3874
4162
  function createGetRemoteEntry(config, ports) {
3875
4163
  return async (remoteEntryUrl, remote) => {
3876
4164
  const { name: remoteName, integrity } = normalizeRemoteRef(remote);
3877
- if (!!remoteName && shouldSkipCachedRemote(remoteEntryUrl, remoteName)) {
3878
- 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
+ );
3879
4171
  return Optional.empty();
3880
4172
  }
4173
+ if (!!remoteName && ports.remoteInfoRepo.contains(remoteName))
4174
+ return warnInitialized(remoteName, remoteEntryUrl);
3881
4175
  try {
3882
4176
  const remoteEntry = integrity ? await ports.remoteEntryProvider.provide(remoteEntryUrl, { integrity }) : await ports.remoteEntryProvider.provide(remoteEntryUrl);
3883
4177
  config.log.debug(
@@ -3892,10 +4186,8 @@ function createGetRemoteEntry(config, ports) {
3892
4186
  }
3893
4187
  config.log.warn(7, errorMsg + " Omitting expected name.");
3894
4188
  }
3895
- if (ports.remoteInfoRepo.contains(remoteEntry.name)) {
3896
- remoteEntry.override = true;
3897
- config.log.debug(7, `Overriding existing remote '${remoteName}' with '${remoteEntryUrl}'.`);
3898
- }
4189
+ if (ports.remoteInfoRepo.contains(remoteEntry.name))
4190
+ return warnInitialized(remoteEntry.name, remoteEntryUrl);
3899
4191
  return Optional.of(checkForSSE(remoteEntry));
3900
4192
  } catch (error) {
3901
4193
  config.log.error(
@@ -3908,10 +4200,18 @@ function createGetRemoteEntry(config, ports) {
3908
4200
  );
3909
4201
  }
3910
4202
  };
3911
- function shouldSkipCachedRemote(remoteEntryUrl, remoteName) {
3912
- return ports.remoteInfoRepo.tryGet(remoteName).map(
3913
- (cachedRemoteInfo) => config.profile.overrideCachedRemotes !== "always" || !config.profile.overrideCachedRemotesIfURLMatches && remoteEntryUrl === join(cachedRemoteInfo.scopeUrl, "remoteEntry.json")
3914
- ).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];
3915
4215
  }
3916
4216
  function checkForSSE(entry) {
3917
4217
  if (config.sse) {
@@ -3931,92 +4231,170 @@ function createGetRemoteEntry(config, ports) {
3931
4231
  // src/lib/core/2.app/steps/update-cache.ts
3932
4232
  function createUpdateCache(config, ports) {
3933
4233
  const storeRemoteEntry = createStoreRemoteEntry(config, ports, 8);
3934
- 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
+ );
3935
4238
  return (remoteEntry) => {
3936
4239
  try {
4240
+ const pending = [];
4241
+ storeRemoteEntry(remoteEntry, (_, external, ctx) => pending.push({ external, ctx }));
4242
+ const decisions = decidePackages(remoteEntry, pending);
3937
4243
  const actions = {};
3938
- if (remoteEntry?.override) removeCachedRemoteEntries(/* @__PURE__ */ new Set([remoteEntry.name]));
3939
- storeRemoteEntry(remoteEntry, (entry, external, ctx) => {
3940
- const { action, provided, sameVersion } = resolveSharedExternal(entry, external, ctx);
3941
- actions[external.packageName] = { action };
3942
- if (action === "skip" && provided) {
3943
- actions[external.packageName].covered = Array.from(provided.keys());
3944
- if (sameVersion) actions[external.packageName].sameVersion = true;
3945
- if (external.shareScope) {
3946
- actions[external.packageName].override = resolveOverrideEntries(
3947
- entry,
3948
- external,
3949
- provided
3950
- );
3951
- }
3952
- }
3953
- });
4244
+ for (const item of pending)
4245
+ actions[item.external.packageName] = applyDecision(remoteEntry, item, decisions.get(item));
3954
4246
  return Promise.resolve({ entry: remoteEntry, actions });
3955
4247
  } catch (error) {
3956
4248
  return Promise.reject(error);
3957
4249
  }
3958
4250
  };
3959
- function resolveSharedExternal(remoteEntry, sharedInfo, {
3960
- tag,
3961
- remote,
3962
- cached: cached2,
3963
- scopeType,
3964
- assertSameVersionCompatibility,
3965
- commit
3966
- }) {
3967
- let action = scopeType === "strict" ? "share" : "skip";
3968
- const sharedVersion = cached2.versions.find((c) => c.action === "share");
3969
- const isCompatible = !sharedVersion || ports.versionCheck.isCompatible(sharedVersion.tag, remote.requiredVersion);
3970
- if (action === "skip" && !isCompatible && remote.strictVersion) {
3971
- action = "scope";
3972
- 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}'`;
3973
- if (config.strict.strictExternalCompatibility) {
3974
- config.log.error(8, errorMsg);
3975
- throw new NFError(`Could not process remote '${remoteEntry.name}'`);
3976
- }
3977
- 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);
3978
4257
  }
3979
- const provided = sharedVersion ? sharedInfo.shareScope ? versionEntries(sharedVersion) : committedEntries(sharedVersion) : void 0;
3980
- const sameVersion = sharedVersion?.tag === tag;
3981
- if (action === "skip" && provided && !sameVersion) {
3982
- const uncovered = uncoveredEntrypoints(remote, provided);
3983
- if (uncovered.length > 0) {
3984
- const msg = `[${sharedInfo.shareScope ?? GLOBAL_SCOPE}][${remoteEntry.name}][${sharedInfo.packageName}] Entrypoints not covered by the shared version: ${uncovered.join(", ")}.`;
3985
- if (config.strict.strictEntryPointCoverage) {
3986
- config.log.error(8, msg);
3987
- throw new NFError(`Could not process remote '${remoteEntry.name}'`);
3988
- }
3989
- if (config.profile.scopeUncoveredEntrypoints) {
3990
- config.log.debug(8, msg);
3991
- action = "scope";
3992
- }
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);
3993
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 };
3994
4316
  }
3995
- const matchingVersion = findVersionForTag(cached2.versions, tag);
3996
- if (action === "scope") {
3997
- remote.cached = true;
3998
- const scoped = cached2.versions.find((v) => v.tag === tag && v.action === "scope");
3999
- if (scoped) scoped.remotes.push(remote);
4000
- else cached2.versions.push({ tag, action, host: false, remotes: [remote] });
4001
- } else if (matchingVersion) {
4002
- assertSameVersionCompatibility(matchingVersion);
4003
- addRemoteToVersion(matchingVersion, remote);
4004
- } else {
4005
- if (!sharedVersion) action = "share";
4006
- remote.cached = action !== "skip";
4007
- 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}'`);
4008
4365
  }
4009
- commit();
4010
- return { action, provided, sameVersion };
4366
+ config.log.warn(8, errorMsg);
4011
4367
  }
4012
- function resolveOverrideEntries(remoteEntry, external, provided) {
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
+ };
4378
+ }
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) {
4013
4394
  return Object.fromEntries(
4014
- Array.from(provided, ([packageName, provider]) => [
4015
- packageName,
4016
- join(
4017
- providerScopeUrl(remoteEntry, external, provider.name),
4018
- provider.entries[packageName]
4019
- )
4395
+ Array.from(files, ([specifier, copy]) => [
4396
+ specifier,
4397
+ join(providerScopeUrl(remoteEntry, external, copy.name), copy.entries[specifier])
4020
4398
  ])
4021
4399
  );
4022
4400
  }
@@ -4031,181 +4409,298 @@ function createUpdateCache(config, ports) {
4031
4409
  }
4032
4410
  }
4033
4411
 
4034
- // src/lib/core/2.app/steps/pooling/pool-dynamic-externals.ts
4035
- function createPoolDynamicExternals(config, ports) {
4036
- return ({ entry, actions }) => {
4037
- const declared = /* @__PURE__ */ new Map();
4038
- for (const external of entry.shared ?? []) {
4039
- const name = external.packageName;
4040
- if (!external.singleton || !actions[name]) continue;
4041
- if (external.shareScope === STRICT_SCOPE) continue;
4042
- const shareScope = external.shareScope ?? GLOBAL_SCOPE;
4043
- let names = declared.get(shareScope);
4044
- if (!names) declared.set(shareScope, names = /* @__PURE__ */ new Set());
4045
- names.add(name);
4046
- }
4047
- if (declared.size === 0) return Promise.resolve({ entry, actions });
4048
- let verdicts = /* @__PURE__ */ new Map();
4049
- const scope = (name, cause) => {
4050
- actions[name].action = "scope";
4051
- delete actions[name].override;
4052
- verdicts.set(name, { cause });
4053
- };
4054
- for (const [shareScope, names] of declared) {
4055
- if (!ports.sharedExternalsRepo.hasPoolState(shareScope)) continue;
4056
- const committed = ports.sharedExternalsRepo.getFromScope(shareScope);
4057
- const pools = buildPools(committed);
4058
- verdicts = /* @__PURE__ */ new Map();
4059
- for (const pool of pools.values()) {
4060
- const mine = pool.filter((member) => names.has(member.name));
4061
- if (mine.length === 0) continue;
4062
- if (mine.some((member) => actions[member.name].action === "scope")) {
4063
- mine.forEach((member) => scope(member.name, "incompatible"));
4064
- continue;
4065
- }
4066
- const asked = gateViews(entry.name, pool);
4067
- const anchor = anchorFor(entry.name, asked);
4068
- if (anchor === "witnessed") continue;
4069
- if (anchor === void 0) {
4070
- const reason = explainDynamic(entry.name, asked);
4071
- const closest = reason.closest ? `closest is '${reason.closest}'` : "no committed build serves any of it";
4072
- config.log.warn(
4073
- 8,
4074
- `[${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] }
4075
4457
  );
4076
- mine.forEach((member) => scope(member.name, "uncovered"));
4077
- continue;
4078
4458
  }
4079
- for (const name of redirect(entry.name, anchor, pool, asked.view, actions, shareScope))
4080
- verdicts.set(name, { servedBy: anchor });
4081
4459
  }
4082
- const written = {};
4083
- for (const [name, verdict] of verdicts) {
4084
- written[name] = recordVerdict(committed[name], entry.name, verdict);
4085
- ports.sharedExternalsRepo.addOrUpdate(name, written[name], shareScope);
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 };
4511
+ };
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);
4086
4519
  }
4087
- syncPoolNames({ ...committed, ...written }, pools, ports.sharedExternalsRepo, shareScope);
4088
4520
  }
4089
- return Promise.resolve({ entry, actions });
4090
- };
4091
- function gateViews(remote, pool) {
4092
- return {
4093
- pool,
4094
- view: committedView(pool),
4095
- wants: consumedMembers(pool).get(remote) ?? [],
4096
- specifiers: consumedSpecifiers(pool).get(remote) ?? /* @__PURE__ */ new Set(),
4097
- acceptance: lazy(() => acceptanceTable(pool, ports.versionCheck.isCompatible))
4098
- };
4099
4521
  }
4100
- function anchorFor(remote, { pool, view, wants, specifiers, acceptance }) {
4101
- const shared = /* @__PURE__ */ new Map();
4102
- for (const [specifier, source] of view.global) shared.set(specifier, source.tag);
4103
- if (isWitnessed(specifiers, shared, view.builds)) return "witnessed";
4104
- const basis = basisPerMember(pool);
4105
- for (const build of candidateOrder(pool, view)) {
4106
- if (build === remote) continue;
4107
- const candidate = view.builds.get(build);
4108
- if (!servesItsOwnFamily(build, pool, basis)) continue;
4109
- if (!covers(candidate.coverage, specifiers)) continue;
4110
- if (!acceptsAll(acceptance(), candidate.instance, remote, wants)) continue;
4111
- return build;
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 };
4112
4538
  }
4113
- return void 0;
4114
- }
4115
- function candidateOrder(pool, view) {
4116
- const serving = /* @__PURE__ */ new Set();
4117
- for (const source of view.global.values()) serving.add(source.remote);
4118
- const hosts = hostRemotes(pool);
4119
- const rank = (build) => serving.has(build) ? 0 : hosts.has(build) ? 1 : 2;
4120
- return [...view.builds.keys()].sort((a, b) => rank(a) - rank(b) || a.localeCompare(b));
4121
- }
4122
- function servesItsOwnFamily(build, pool, basis) {
4123
- let ships2 = 0;
4124
- let winsAll = true;
4125
- let allScoped = true;
4126
- for (const member of pool) {
4127
- for (const version of member.external.versions) {
4128
- const meta = version.remotes.find((r) => r.name === build);
4129
- if (!meta) continue;
4130
- if (meta.servedBy !== void 0) return false;
4131
- ships2++;
4132
- if (version.action !== "scope") allScoped = false;
4133
- if (basis.get(member.name) !== build) winsAll = false;
4134
- }
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);
4135
4551
  }
4136
- return ships2 > 0 && (winsAll || allScoped);
4137
- }
4138
- function explainDynamic(remote, { view, wants, specifiers, acceptance }) {
4139
- return explainSelfServe(remote, wants, specifiers, {
4140
- builds: view.builds,
4141
- acceptance: acceptance()
4142
- });
4552
+ covered.add(specifier);
4143
4553
  }
4144
- function redirect(remote, anchor, pool, view, actions, shareScope) {
4145
- const files = view.builds.get(anchor).coverage;
4146
- const scopeUrl = ports.remoteInfoRepo.tryGet(anchor).get()?.scopeUrl;
4147
- if (!scopeUrl) {
4148
- config.log.warn(
4149
- 8,
4150
- `[${shareScope}][${remote}] '${anchor}' is not in the cache, so its files cannot be mapped.`
4151
- );
4152
- 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 };
4153
4565
  }
4154
- const basis = basisPerMember(pool);
4155
- const served = [];
4156
- for (const member of pool) {
4157
- const action = actions[member.name];
4158
- if (!action || action.action !== "skip") continue;
4159
- const own = member.external.versions.flatMap((v) => v.remotes).find((r) => r.name === remote);
4160
- if (!own) continue;
4161
- const specifiers = Object.keys(own.entries);
4162
- if (specifiers.length === 0) continue;
4163
- const override = {};
4164
- for (const specifier of specifiers) {
4165
- if (view.global.get(specifier)?.remote === anchor) continue;
4166
- const file = files.get(specifier);
4167
- if (file) override[specifier] = join(scopeUrl, file);
4168
- }
4169
- action.covered = specifiers;
4170
- if (Object.keys(override).length > 0) action.override = override;
4171
- if (basis.get(member.name) !== anchor) served.push(member.name);
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);
4172
4575
  }
4173
- return served;
4174
- }
4175
- function recordVerdict(external, remote, verdict) {
4176
- if ("servedBy" in verdict) {
4177
- return {
4178
- ...external,
4179
- versions: external.versions.map((v) => ({
4180
- ...v,
4181
- remotes: v.remotes.map(
4182
- (r) => r.name === remote ? { ...r, servedBy: verdict.servedBy } : r
4183
- )
4184
- }))
4185
- };
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 };
4186
4582
  }
4187
- const moved = [];
4188
- const versions = external.versions.map((v) => {
4189
- const own = v.remotes.find((r) => r.name === remote);
4190
- if (!own) return v;
4191
- const { servedBy: _anchor, ...rest } = own;
4192
- const meta = { ...rest, cached: true, poolCause: verdict.cause };
4193
- if (v.action === "scope")
4194
- return { ...v, remotes: v.remotes.map((r) => r === own ? meta : r) };
4195
- moved.push({ tag: v.tag, meta });
4196
- return { ...v, remotes: v.remotes.filter((r) => r !== own) };
4197
- }).filter((v) => v.remotes.length > 0);
4198
- for (const { tag, meta } of moved) {
4199
- const at = versions.findIndex((v) => v.tag === tag && v.action === "scope");
4200
- if (at >= 0) versions[at] = { ...versions[at], remotes: [...versions[at].remotes, meta] };
4201
- else versions.push({ tag, action: "scope", host: false, remotes: [meta] });
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);
4202
4604
  }
4203
- return {
4204
- ...external,
4205
- versions: versions.sort((a, b) => ports.versionCheck.compare(b.tag, a.tag))
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
+ }
4206
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;
4650
+ }
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
+ );
4207
4671
  }
4208
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
+ }
4209
4704
 
4210
4705
  // src/lib/core/2.app/steps/convert-to-import-map.ts
4211
4706
  function createConvertToImportMap(config, ports) {
@@ -4242,27 +4737,14 @@ function createConvertToImportMap(config, ports) {
4242
4737
  return;
4243
4738
  }
4244
4739
  if (actions[external.packageName].action === "skip") {
4245
- const { override, covered, sameVersion } = actions[external.packageName];
4740
+ const { override } = actions[external.packageName];
4246
4741
  if (override) {
4247
4742
  Object.entries(override).forEach(([packageName, url]) => {
4248
4743
  addToScopes(remoteEntryScope, packageName, url, importMap);
4249
4744
  });
4250
- serveUncovered(
4251
- remoteEntry,
4252
- external,
4253
- covered ?? Object.keys(override),
4254
- remoteEntryScope,
4255
- importMap,
4256
- sameVersion
4257
- );
4258
- return;
4259
- }
4260
- if (!external.shareScope) {
4261
- if (covered) {
4262
- serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap, sameVersion);
4263
- }
4264
4745
  return;
4265
4746
  }
4747
+ if (!external.shareScope) return;
4266
4748
  log.error(
4267
4749
  9,
4268
4750
  `[${remoteEntry.name}][${external.packageName}] shareScope skip has no override.`
@@ -4327,27 +4809,6 @@ function createConvertToImportMap(config, ports) {
4327
4809
  });
4328
4810
  return importMap;
4329
4811
  }
4330
- function serveUncovered(remoteEntry, external, covered, remoteEntryScope, importMap, sameVersion = false) {
4331
- const provided = new Set(covered);
4332
- Object.entries(external.entries).forEach(([packageName, fileName]) => {
4333
- if (provided.has(packageName)) return;
4334
- if (!sameVersion && (config.strict.strictEntryPointCoverage || config.profile.scopeUncoveredEntrypoints)) {
4335
- warnUncoveredEntrypoint(remoteEntry.name, external.packageName, packageName);
4336
- return;
4337
- }
4338
- const url = join(remoteEntryScope, fileName);
4339
- addToScopes(remoteEntryScope, packageName, url, importMap);
4340
- addIntegrity(importMap, url, remoteEntry.integrity, fileName);
4341
- });
4342
- }
4343
- function warnUncoveredEntrypoint(remoteName, externalName, packageName) {
4344
- const msg = `[${remoteName}][${externalName}] Entrypoint '${packageName}' is not covered by the override.`;
4345
- if (config.strict.strictEntryPointCoverage || config.strict.strictImportMap) {
4346
- log.error(9, msg);
4347
- throw new NFError("Could not create ImportMap.");
4348
- }
4349
- log.warn(9, msg);
4350
- }
4351
4812
  function addIntegrity(importMap, url, integrityMap, file) {
4352
4813
  const hash = integrityMap?.[file];
4353
4814
  if (!hash) return;
@@ -4363,11 +4824,12 @@ var createInitDrivers = ({
4363
4824
  }) => ({
4364
4825
  getRemoteEntries: createGetRemoteEntries(config, adapters),
4365
4826
  processRemoteEntries: createProcessRemoteEntries(config, adapters),
4366
- markPoolsForReelection: createMarkPoolsForReelection(config, adapters),
4367
4827
  determineSharedExternals: createDetermineSharedExternals(config, adapters),
4368
4828
  poolSharedExternals: createPoolSharedExternals(config, adapters),
4829
+ decideServingBuilds: createDecideServingBuilds(config, adapters),
4369
4830
  generateImportMap: createGenerateImportMap(config, adapters),
4370
4831
  commitChanges: createCommitChanges(config, adapters),
4832
+ rollbackChanges: createRollbackChanges(config, adapters),
4371
4833
  exposeModuleLoader: createExposeModuleLoader(config, adapters),
4372
4834
  getRemoteEntry: createGetRemoteEntry(config, adapters),
4373
4835
  updateCache: createUpdateCache(config, adapters),
@@ -4387,14 +4849,6 @@ var INIT_FLOW_FACTORY = ({
4387
4849
  };
4388
4850
 
4389
4851
  // src/lib/core/5.di/federation-result.factory.ts
4390
- var createStateDump = (config, adapters) => (msg) => config.log.debug(0, msg, {
4391
- remotes: { ...adapters.remoteInfoRepo.getAll() },
4392
- "shared-externals": adapters.sharedExternalsRepo.getScopes({ includeGlobal: true }).reduce(
4393
- (acc, scope) => ({ ...acc, [scope]: adapters.sharedExternalsRepo.getFromScope(scope) }),
4394
- {}
4395
- ),
4396
- "scoped-externals": adapters.scopedExternalsRepo.getAll()
4397
- });
4398
4852
  var createFederationResult = ({
4399
4853
  config,
4400
4854
  adapters,
@@ -4402,11 +4856,8 @@ var createFederationResult = ({
4402
4856
  initRemoteEntryFlow,
4403
4857
  afterInitRemoteEntry
4404
4858
  }) => {
4405
- const stateDump = createStateDump(config, adapters);
4406
4859
  const initRemoteEntry = async (remoteEntryUrl, remote) => {
4407
- const remoteName = typeof remote === "string" ? remote : remote?.name;
4408
4860
  return initRemoteEntryFlow(remoteEntryUrl, remote).catch((e) => {
4409
- stateDump(`[dynamic-init][${remoteName ?? remoteEntryUrl}] STATE DUMP`);
4410
4861
  if (config.strict.strictRemoteEntry) return Promise.reject(e);
4411
4862
  else console.warn("Failed to initialize remote entry, continuing anyway.");
4412
4863
  return Promise.resolve();