@softarc/native-federation-orchestrator 4.5.1 → 4.5.2

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.
@@ -1610,6 +1610,14 @@ function createDetermineSharedExternals(config, ports) {
1610
1610
  }
1611
1611
  return Promise.resolve();
1612
1612
  };
1613
+ function uncoveredTears(external, winner) {
1614
+ const basis = winner.remotes[0].entries;
1615
+ return external.versions.reduce((sum, v) => {
1616
+ if (v === winner) return sum;
1617
+ if (!ports.versionCheck.isCompatible(winner.tag, v.remotes[0].requiredVersion)) return sum;
1618
+ return sum + v.remotes.reduce((n, r) => n + Object.keys(r.entries).filter((e) => !(e in basis)).length, 0);
1619
+ }, 0);
1620
+ }
1613
1621
  function setVersionActions(externalName, external) {
1614
1622
  if (external.versions.length === 1) {
1615
1623
  external.versions[0].action = "share";
@@ -1622,12 +1630,15 @@ function createDetermineSharedExternals(config, ports) {
1622
1630
  }
1623
1631
  if (!sharedVersion) {
1624
1632
  let leastExtraDownloads = Number.MAX_VALUE;
1633
+ let leastTears = Number.MAX_VALUE;
1625
1634
  external.versions.forEach((vA) => {
1626
1635
  const extraDownloads = external.versions.filter(
1627
1636
  (vB) => !vB.remotes[0].cached && vB.remotes[0].strictVersion && !ports.versionCheck.isCompatible(vA.tag, vB.remotes[0].requiredVersion)
1628
1637
  ).length;
1629
- if (extraDownloads < leastExtraDownloads) {
1638
+ const tears = uncoveredTears(external, vA);
1639
+ if (extraDownloads < leastExtraDownloads || extraDownloads === leastExtraDownloads && tears < leastTears) {
1630
1640
  leastExtraDownloads = extraDownloads;
1641
+ leastTears = tears;
1631
1642
  sharedVersion = vA;
1632
1643
  }
1633
1644
  });
@@ -1650,6 +1661,22 @@ function createDetermineSharedExternals(config, ports) {
1650
1661
  v.action = v.remotes[0].strictVersion ? "scope" : "skip";
1651
1662
  });
1652
1663
  sharedVersion.action = "share";
1664
+ if (config.strict.strictEntryPointCoverage) {
1665
+ const basis = sharedVersion.remotes[0].entries;
1666
+ external.versions.forEach((v) => {
1667
+ if (v.action !== "skip") return;
1668
+ const uncovered = v.remotes.some(
1669
+ (r) => Object.keys(r.entries).some((entrypoint) => !(entrypoint in basis))
1670
+ );
1671
+ if (uncovered) {
1672
+ v.action = "scope";
1673
+ config.log.debug(
1674
+ 3,
1675
+ `[${externalName}@${v.tag}] Scoped: entrypoints not covered by shared ${externalName}@${sharedVersion.tag}.`
1676
+ );
1677
+ }
1678
+ });
1679
+ }
1653
1680
  external.dirty = false;
1654
1681
  return external;
1655
1682
  }
@@ -1757,9 +1784,6 @@ function buildPools(sharedExternals, useAutoExternalPooling, log) {
1757
1784
  }
1758
1785
 
1759
1786
  // src/lib/core/2.app/steps/pooling/pool.util.ts
1760
- function versionForRemote(member, remote) {
1761
- return member.external.versions.find((v) => v.remotes.some((r) => r.name === remote));
1762
- }
1763
1787
  function remotesInPool(members) {
1764
1788
  return [
1765
1789
  ...new Set(members.flatMap((m) => m.external.versions.flatMap((v) => v.remotes.map((r) => r.name))))
@@ -1767,57 +1791,12 @@ function remotesInPool(members) {
1767
1791
  }
1768
1792
 
1769
1793
  // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
1770
- function poolWinners(members) {
1771
- return members.map((member) => {
1772
- const share = member.external.versions.find((v) => v.action === "share");
1773
- return {
1774
- member,
1775
- tag: share?.tag ?? "",
1776
- providers: new Set((share?.remotes ?? []).map((r) => r.name))
1777
- };
1778
- });
1779
- }
1780
- function selectCoverageAnchor(winners) {
1781
- const coverage = /* @__PURE__ */ new Map();
1782
- for (const winner of winners)
1783
- for (const remote of winner.providers) coverage.set(remote, (coverage.get(remote) ?? 0) + 1);
1784
- if (coverage.size === 0) return void 0;
1785
- const anchorRemote = [...coverage.keys()].sort(
1786
- (a, b) => coverage.get(b) - coverage.get(a) || a.localeCompare(b)
1787
- )[0];
1788
- return {
1789
- anchorRemote,
1790
- tagPerMember: Object.fromEntries(
1791
- winners.filter((w) => w.providers.has(anchorRemote)).map((w) => [w.member.name, w.tag])
1792
- )
1793
- };
1794
- }
1795
- function classifyPoolConservative(members, anchor) {
1796
- const classification = /* @__PURE__ */ new Map();
1797
- for (const remote of remotesInPool(members)) {
1798
- let coverageForced = false;
1799
- let verdict = "follow";
1800
- for (const member of members) {
1801
- const version = versionForRemote(member, remote);
1802
- if (!version) continue;
1803
- const anchorTag = anchor.tagPerMember[member.name];
1804
- if (!anchorTag) {
1805
- coverageForced = true;
1806
- continue;
1807
- }
1808
- if (version.tag === anchorTag) continue;
1809
- if (version.action === "scope") {
1810
- verdict = "scope-incompat";
1811
- break;
1812
- }
1813
- }
1814
- classification.set(
1815
- remote,
1816
- verdict === "scope-incompat" ? "scope-incompat" : coverageForced ? "scope-coverage" : "follow"
1817
- );
1818
- }
1819
- classification.set(anchor.anchorRemote, "follow");
1820
- return classification;
1794
+ function islandedRemotes(members) {
1795
+ const islanded = /* @__PURE__ */ new Set();
1796
+ for (const member of members)
1797
+ for (const version of member.external.versions)
1798
+ if (version.action === "scope") for (const remote of version.remotes) islanded.add(remote.name);
1799
+ return islanded;
1821
1800
  }
1822
1801
  function createPoolSharedExternals(config, ports) {
1823
1802
  return () => {
@@ -1853,73 +1832,57 @@ function createPoolSharedExternals(config, ports) {
1853
1832
  if (members.length < 2) return;
1854
1833
  const allRemotes = remotesInPool(members);
1855
1834
  if (allRemotes.length < 2) return;
1856
- const anchor = selectCoverageAnchor(poolWinners(members));
1857
- if (!anchor) return;
1858
- const classification = classifyPoolConservative(members, anchor);
1835
+ const islanded = islandedRemotes(members);
1859
1836
  config.log.debug(
1860
1837
  3,
1861
- `[${scope}][pool:${poolName}] members=[${members.map((m) => m.name).join(", ")}], anchor=${anchor.anchorRemote}, remotes={${allRemotes.map((r) => `${r}:${classification.get(r)}`).join(", ")}}`
1838
+ `[${scope}][pool:${poolName}] ${members.length} members across ${allRemotes.length} remotes, islanded={${[...islanded].join(", ") || "\u2205"}}
1839
+ ` + members.map((m) => ` - ${m.name}`).join("\n")
1862
1840
  );
1863
- const incompatForced = allRemotes.some((r) => classification.get(r) === "scope-incompat");
1864
- if (incompatForced && config.strict.strictExternalCompatibility) {
1841
+ if (islanded.size > 0 && config.strict.strictExternalCompatibility) {
1865
1842
  config.log.error(
1866
1843
  3,
1867
- `[${scope}][pool:${poolName}] A remote is version-incompatible with anchor '${anchor.anchorRemote}'.`
1844
+ `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${[...islanded].join(", ")}}.`
1868
1845
  );
1869
1846
  throw new NFError(`Could not pool '${poolName}' in scope ${scope}.`);
1870
1847
  }
1871
1848
  for (const member of members) {
1872
- const rebuilt = rebuildMember(member, anchor, classification);
1849
+ const rebuilt = rebuildMember(member, islanded);
1873
1850
  warnIfScopedOnly(poolName, member.name, rebuilt, scope);
1874
1851
  ports.sharedExternalsRepo.addOrUpdate(member.name, rebuilt, scope);
1875
1852
  }
1876
1853
  }
1877
1854
  function warnIfScopedOnly(poolName, memberName, rebuilt, scope) {
1878
1855
  if (rebuilt.versions.some((v) => v.action === "share")) return;
1879
- const copies = rebuilt.versions.reduce((n, v) => n + v.remotes.length, 0);
1856
+ const consumers = new Set(rebuilt.versions.flatMap((v) => v.remotes.map((r) => r.name))).size;
1857
+ if (consumers < 2) return;
1880
1858
  config.log.warn(
1881
1859
  3,
1882
- `[${scope}][pool:${poolName}] '${memberName}' is scoped-only \u2014 no coherent shared build provides it; ${copies} remotes download their own copy.`
1860
+ `[${scope}][pool:${poolName}] '${memberName}' is scoped-only \u2014 no coherent shared build provides it; ${consumers} remotes download their own copy.`
1883
1861
  );
1884
1862
  }
1885
- function rebuildMember(member, anchor, classification) {
1886
- const anchorTag = anchor.tagPerMember[member.name];
1863
+ function rebuildMember(member, islanded) {
1887
1864
  const entries = member.external.versions.flatMap(
1888
- (v) => v.remotes.map((meta) => ({ remote: meta.name, tag: v.tag, host: v.host, meta }))
1889
- );
1890
- if (!anchorTag) {
1891
- return {
1892
- dirty: false,
1893
- versions: entries.length > 0 ? [
1894
- {
1895
- tag: entries[0].tag,
1896
- host: false,
1897
- action: "scope",
1898
- remotes: entries.map((e) => e.meta)
1899
- }
1900
- ] : []
1901
- };
1902
- }
1903
- const anchorMeta = entries.find(
1904
- (e) => e.remote === anchor.anchorRemote && e.tag === anchorTag
1905
- ).meta;
1906
- const anchorHost = member.external.versions.find((v) => v.tag === anchorTag)?.host ?? false;
1907
- const scopes = (e) => {
1908
- const cls = classification.get(e.remote);
1909
- return cls === "scope-incompat" || cls === "scope-coverage" && e.tag !== anchorTag;
1910
- };
1911
- const shareOnAnchor = entries.filter(
1912
- (e) => e.remote !== anchor.anchorRemote && e.tag === anchorTag && !scopes(e)
1865
+ (v) => v.remotes.map((meta) => ({ remote: meta.name, tag: v.tag, host: v.host, action: v.action, meta }))
1913
1866
  );
1914
- const shareVersion = {
1915
- tag: anchorTag,
1916
- host: anchorHost,
1917
- action: "share",
1918
- remotes: [anchorMeta, ...shareOnAnchor.map((e) => e.meta)]
1919
- };
1867
+ const isScoped = (e) => islanded.has(e.remote) || e.action === "scope";
1868
+ let scoped = entries.filter(isScoped);
1869
+ let clean = entries.filter((e) => !isScoped(e));
1870
+ if (!clean.some((e) => e.action === "share")) {
1871
+ scoped = [...scoped, ...clean];
1872
+ clean = [];
1873
+ }
1874
+ const shareEntries = clean.filter((e) => e.action === "share");
1875
+ const shareVersion = shareEntries.length > 0 ? [
1876
+ {
1877
+ tag: shareEntries[0].tag,
1878
+ host: shareEntries[0].host,
1879
+ action: "share",
1880
+ remotes: shareEntries.map((e) => e.meta)
1881
+ }
1882
+ ] : [];
1920
1883
  const skipByTag = /* @__PURE__ */ new Map();
1921
- for (const e of entries) {
1922
- if (e.tag === anchorTag || scopes(e)) continue;
1884
+ for (const e of clean) {
1885
+ if (e.action !== "skip") continue;
1923
1886
  const version = skipByTag.get(e.tag) ?? {
1924
1887
  tag: e.tag,
1925
1888
  host: e.host,
@@ -1929,16 +1892,25 @@ function createPoolSharedExternals(config, ports) {
1929
1892
  version.remotes.push(e.meta);
1930
1893
  skipByTag.set(e.tag, version);
1931
1894
  }
1932
- const scoped = entries.filter((e) => scopes(e));
1933
- const scopeVersion = scoped.length > 0 ? [{ tag: anchorTag, host: false, action: "scope", remotes: scoped.map((e) => e.meta) }] : [];
1895
+ const scopeByTag = /* @__PURE__ */ new Map();
1896
+ for (const e of scoped) {
1897
+ const version = scopeByTag.get(e.tag) ?? {
1898
+ tag: e.tag,
1899
+ host: false,
1900
+ action: "scope",
1901
+ remotes: []
1902
+ };
1903
+ version.remotes.push(e.meta);
1904
+ scopeByTag.set(e.tag, version);
1905
+ }
1934
1906
  return {
1935
1907
  dirty: false,
1936
- versions: [shareVersion, ...skipByTag.values(), ...scopeVersion]
1908
+ versions: [...shareVersion, ...skipByTag.values(), ...scopeByTag.values()]
1937
1909
  };
1938
1910
  }
1939
1911
  }
1940
1912
 
1941
- // node_modules/.pnpm/@softarc+native-federation@4.3.1_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/build-notification-options.contract.js
1913
+ // node_modules/.pnpm/@softarc+native-federation@4.3.2_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/build-notification-options.contract.js
1942
1914
  var BuildNotificationType = /* @__PURE__ */ ((BuildNotificationType2) => {
1943
1915
  BuildNotificationType2["COMPLETED"] = "federation-rebuild-complete";
1944
1916
  BuildNotificationType2["ERROR"] = "federation-rebuild-error";
@@ -1946,7 +1918,7 @@ var BuildNotificationType = /* @__PURE__ */ ((BuildNotificationType2) => {
1946
1918
  return BuildNotificationType2;
1947
1919
  })(BuildNotificationType || {});
1948
1920
 
1949
- // node_modules/.pnpm/@softarc+native-federation@4.3.1_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
1921
+ // node_modules/.pnpm/@softarc+native-federation@4.3.2_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
1950
1922
  var CHUNK_PREFIX = "@nf-internal";
1951
1923
  function toChunkImport(fileName) {
1952
1924
  if (fileName.startsWith("./")) {
@@ -2053,6 +2025,17 @@ function createGenerateImportMap(config, ports) {
2053
2025
  for (const m of mappings) {
2054
2026
  addToScope(importMap, rScope, { [m.packageName]: m.url });
2055
2027
  }
2028
+ for (const [packageName, file] of Object.entries(r.entries)) {
2029
+ if (packageName in source.entries) continue;
2030
+ if (config.strict.strictEntryPointCoverage) {
2031
+ warnUncoveredEntrypoint(shareScope, externalName, r.name, packageName);
2032
+ continue;
2033
+ }
2034
+ const url = join(rScope, file);
2035
+ addToScope(importMap, rScope, { [packageName]: url });
2036
+ addIntegrity(importMap, url, r.name, file);
2037
+ r.cached = true;
2038
+ }
2056
2039
  });
2057
2040
  for (const m of mappings) {
2058
2041
  addIntegrity(importMap, m.url, m.name, m.file);
@@ -2120,10 +2103,35 @@ function createGenerateImportMap(config, ports) {
2120
2103
  registerBundleChunks(chunkBundles, version.remotes[0].name, version.remotes[0].bundle);
2121
2104
  version.remotes[0].cached = true;
2122
2105
  }
2106
+ for (const version of external.versions) {
2107
+ if (version.action !== "skip") continue;
2108
+ const remote = version.remotes[0];
2109
+ for (const [packageName, file] of Object.entries(remote.entries)) {
2110
+ if (importMap.imports[packageName]) continue;
2111
+ if (config.strict.strictEntryPointCoverage) {
2112
+ warnUncoveredEntrypoint(GLOBAL_SCOPE, externalName, remote.name, packageName);
2113
+ continue;
2114
+ }
2115
+ const scope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
2116
+ const url = join(scope, file);
2117
+ addToGlobal(importMap, { [packageName]: url });
2118
+ addIntegrity(importMap, url, remote.name, file);
2119
+ registerBundleChunks(chunkBundles, remote.name, remote.bundle);
2120
+ remote.cached = true;
2121
+ }
2122
+ }
2123
2123
  ports.sharedExternalsRepo.addOrUpdate(externalName, external);
2124
2124
  }
2125
2125
  return importMap;
2126
2126
  }
2127
+ function warnUncoveredEntrypoint(scope, externalName, remoteName, packageName) {
2128
+ const msg = `[${scope}][${externalName}][${remoteName}] Entrypoint '${packageName}' is not covered by the shared version.`;
2129
+ if (config.strict.strictImportMap) {
2130
+ config.log.error(4, msg);
2131
+ throw new NFError("Could not create ImportMap.");
2132
+ }
2133
+ config.log.warn(4, msg);
2134
+ }
2127
2135
  function notifyDuplicateGlobalExternal(externalName) {
2128
2136
  if (config.strict.strictImportMap) {
2129
2137
  config.log.error(4, `[${externalName}] Shared external has multiple shared versions.`);
@@ -2385,6 +2393,9 @@ function createUpdateCache(config, ports) {
2385
2393
  }
2386
2394
  config.log.warn(8, errorMsg);
2387
2395
  }
2396
+ if (action === "skip" && config.strict.strictEntryPointCoverage && sharedVersion && Object.keys(remote.entries).some((e) => !(e in sharedVersion.remotes[0].entries))) {
2397
+ action = "scope";
2398
+ }
2388
2399
  const matchingVersion = cached.versions.find((cached2) => cached2.tag === tag);
2389
2400
  if (matchingVersion) {
2390
2401
  assertSameVersionCompatibility(matchingVersion);
@@ -2449,8 +2460,6 @@ function createPoolDynamicExternals(config) {
2449
2460
  const memberActions = members.map((name) => actions[name].action);
2450
2461
  if (memberActions.includes("scope")) {
2451
2462
  members.forEach(scope);
2452
- } else if (memberActions.includes("share") && memberActions.includes("skip")) {
2453
- members.filter((name) => actions[name].action === "share").forEach(scope);
2454
2463
  }
2455
2464
  }
2456
2465
  }
@@ -2499,6 +2508,16 @@ function createConvertToImportMap(config, ports) {
2499
2508
  Object.entries(override).forEach(([packageName, url]) => {
2500
2509
  addToScopes(remoteEntryScope, packageName, url, importMap);
2501
2510
  });
2511
+ Object.entries(external.entries).forEach(([packageName, fileName]) => {
2512
+ if (packageName in override) return;
2513
+ if (config.strict.strictEntryPointCoverage) {
2514
+ warnUncoveredEntrypoint(remoteEntry.name, external.packageName, packageName);
2515
+ return;
2516
+ }
2517
+ const url = join(remoteEntryScope, fileName);
2518
+ addToScopes(remoteEntryScope, packageName, url, importMap);
2519
+ addIntegrity(importMap, url, integrityMap, fileName);
2520
+ });
2502
2521
  return;
2503
2522
  }
2504
2523
  log.error(
@@ -2565,6 +2584,14 @@ function createConvertToImportMap(config, ports) {
2565
2584
  });
2566
2585
  return importMap;
2567
2586
  }
2587
+ function warnUncoveredEntrypoint(remoteName, externalName, packageName) {
2588
+ const msg = `[${remoteName}][${externalName}] Entrypoint '${packageName}' is not covered by the override.`;
2589
+ if (config.strict.strictImportMap) {
2590
+ log.error(9, msg);
2591
+ throw new NFError("Could not create ImportMap.");
2592
+ }
2593
+ log.warn(9, msg);
2594
+ }
2568
2595
  function addIntegrity(importMap, url, integrityMap, file) {
2569
2596
  const hash = integrityMap?.[file];
2570
2597
  if (!hash) return;
@@ -2698,7 +2725,7 @@ var createManifestProvider = () => {
2698
2725
  };
2699
2726
  };
2700
2727
 
2701
- // node_modules/.pnpm/@softarc+native-federation@4.3.1_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/utils/normalize.js
2728
+ // node_modules/.pnpm/@softarc+native-federation@4.3.2_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/utils/normalize.js
2702
2729
  function inferPackageFromSecondary(secondary) {
2703
2730
  const parts = secondary.split("/");
2704
2731
  if (secondary.startsWith("@") && parts.length >= 2) {
@@ -2707,7 +2734,7 @@ function inferPackageFromSecondary(secondary) {
2707
2734
  return parts[0];
2708
2735
  }
2709
2736
 
2710
- // node_modules/.pnpm/@softarc+native-federation@4.3.1_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/core/output/densify-externals.js
2737
+ // node_modules/.pnpm/@softarc+native-federation@4.3.2_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/core/output/densify-externals.js
2711
2738
  function isDense(entry) {
2712
2739
  return "entries" in entry;
2713
2740
  }
@@ -2748,6 +2775,7 @@ function densifyExternals(shared) {
2748
2775
  if (entry.version !== void 0) dense.version = entry.version;
2749
2776
  if (entry.shareScope !== void 0) dense.shareScope = entry.shareScope;
2750
2777
  if (entry.bundle !== void 0) dense.bundle = entry.bundle;
2778
+ if (entry.pool !== void 0) dense.pool = entry.pool;
2751
2779
  if (entry.dev !== void 0) dense.dev = entry.dev;
2752
2780
  groupIndex.set(key, result.length);
2753
2781
  result.push(dense);
@@ -3223,13 +3251,15 @@ var createModeConfig = (override) => {
3223
3251
  strictExternalCompatibility: override.strict,
3224
3252
  strictExternalSameVersionCompatibility: override.strict,
3225
3253
  strictExternalVersion: override.strict,
3226
- strictImportMap: override.strict
3254
+ strictImportMap: override.strict,
3255
+ strictEntryPointCoverage: override.strict
3227
3256
  } : {
3228
3257
  strictRemoteEntry: override.strict?.strictRemoteEntry ?? false,
3229
3258
  strictExternalCompatibility: override.strict?.strictExternalCompatibility ?? false,
3230
3259
  strictExternalSameVersionCompatibility: override.strict?.strictExternalSameVersionCompatibility ?? false,
3231
3260
  strictExternalVersion: override.strict?.strictExternalVersion ?? false,
3232
- strictImportMap: override.strict?.strictImportMap ?? false
3261
+ strictImportMap: override.strict?.strictImportMap ?? false,
3262
+ strictEntryPointCoverage: override.strict?.strictEntryPointCoverage ?? false
3233
3263
  };
3234
3264
  return {
3235
3265
  strict: strictnessConfig,