@softarc/native-federation-orchestrator 4.5.0 → 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.
package/fesm2022/node.mjs CHANGED
@@ -1868,13 +1868,15 @@ var createModeConfig = (override) => {
1868
1868
  strictExternalCompatibility: override.strict,
1869
1869
  strictExternalSameVersionCompatibility: override.strict,
1870
1870
  strictExternalVersion: override.strict,
1871
- strictImportMap: override.strict
1871
+ strictImportMap: override.strict,
1872
+ strictEntryPointCoverage: override.strict
1872
1873
  } : {
1873
1874
  strictRemoteEntry: override.strict?.strictRemoteEntry ?? false,
1874
1875
  strictExternalCompatibility: override.strict?.strictExternalCompatibility ?? false,
1875
1876
  strictExternalSameVersionCompatibility: override.strict?.strictExternalSameVersionCompatibility ?? false,
1876
1877
  strictExternalVersion: override.strict?.strictExternalVersion ?? false,
1877
- strictImportMap: override.strict?.strictImportMap ?? false
1878
+ strictImportMap: override.strict?.strictImportMap ?? false,
1879
+ strictEntryPointCoverage: override.strict?.strictEntryPointCoverage ?? false
1878
1880
  };
1879
1881
  return {
1880
1882
  strict: strictnessConfig,
@@ -2200,6 +2202,14 @@ function createDetermineSharedExternals(config, ports) {
2200
2202
  }
2201
2203
  return Promise.resolve();
2202
2204
  };
2205
+ function uncoveredTears(external, winner) {
2206
+ const basis = winner.remotes[0].entries;
2207
+ return external.versions.reduce((sum, v) => {
2208
+ if (v === winner) return sum;
2209
+ if (!ports.versionCheck.isCompatible(winner.tag, v.remotes[0].requiredVersion)) return sum;
2210
+ return sum + v.remotes.reduce((n, r) => n + Object.keys(r.entries).filter((e) => !(e in basis)).length, 0);
2211
+ }, 0);
2212
+ }
2203
2213
  function setVersionActions(externalName, external) {
2204
2214
  if (external.versions.length === 1) {
2205
2215
  external.versions[0].action = "share";
@@ -2212,12 +2222,15 @@ function createDetermineSharedExternals(config, ports) {
2212
2222
  }
2213
2223
  if (!sharedVersion) {
2214
2224
  let leastExtraDownloads = Number.MAX_VALUE;
2225
+ let leastTears = Number.MAX_VALUE;
2215
2226
  external.versions.forEach((vA) => {
2216
2227
  const extraDownloads = external.versions.filter(
2217
2228
  (vB) => !vB.remotes[0].cached && vB.remotes[0].strictVersion && !ports.versionCheck.isCompatible(vA.tag, vB.remotes[0].requiredVersion)
2218
2229
  ).length;
2219
- if (extraDownloads < leastExtraDownloads) {
2230
+ const tears = uncoveredTears(external, vA);
2231
+ if (extraDownloads < leastExtraDownloads || extraDownloads === leastExtraDownloads && tears < leastTears) {
2220
2232
  leastExtraDownloads = extraDownloads;
2233
+ leastTears = tears;
2221
2234
  sharedVersion = vA;
2222
2235
  }
2223
2236
  });
@@ -2240,6 +2253,22 @@ function createDetermineSharedExternals(config, ports) {
2240
2253
  v.action = v.remotes[0].strictVersion ? "scope" : "skip";
2241
2254
  });
2242
2255
  sharedVersion.action = "share";
2256
+ if (config.strict.strictEntryPointCoverage) {
2257
+ const basis = sharedVersion.remotes[0].entries;
2258
+ external.versions.forEach((v) => {
2259
+ if (v.action !== "skip") return;
2260
+ const uncovered = v.remotes.some(
2261
+ (r) => Object.keys(r.entries).some((entrypoint) => !(entrypoint in basis))
2262
+ );
2263
+ if (uncovered) {
2264
+ v.action = "scope";
2265
+ config.log.debug(
2266
+ 3,
2267
+ `[${externalName}@${v.tag}] Scoped: entrypoints not covered by shared ${externalName}@${sharedVersion.tag}.`
2268
+ );
2269
+ }
2270
+ });
2271
+ }
2243
2272
  external.dirty = false;
2244
2273
  return external;
2245
2274
  }
@@ -2347,9 +2376,6 @@ function buildPools(sharedExternals, useAutoExternalPooling, log) {
2347
2376
  }
2348
2377
 
2349
2378
  // src/lib/core/2.app/steps/pooling/pool.util.ts
2350
- function versionForRemote(member, remote) {
2351
- return member.external.versions.find((v) => v.remotes.some((r) => r.name === remote));
2352
- }
2353
2379
  function remotesInPool(members) {
2354
2380
  return [
2355
2381
  ...new Set(members.flatMap((m) => m.external.versions.flatMap((v) => v.remotes.map((r) => r.name))))
@@ -2357,57 +2383,12 @@ function remotesInPool(members) {
2357
2383
  }
2358
2384
 
2359
2385
  // src/lib/core/2.app/steps/pooling/pool-shared-externals.ts
2360
- function poolWinners(members) {
2361
- return members.map((member) => {
2362
- const share = member.external.versions.find((v) => v.action === "share");
2363
- return {
2364
- member,
2365
- tag: share?.tag ?? "",
2366
- providers: new Set((share?.remotes ?? []).map((r) => r.name))
2367
- };
2368
- });
2369
- }
2370
- function selectCoverageAnchor(winners) {
2371
- const coverage = /* @__PURE__ */ new Map();
2372
- for (const winner of winners)
2373
- for (const remote of winner.providers) coverage.set(remote, (coverage.get(remote) ?? 0) + 1);
2374
- if (coverage.size === 0) return void 0;
2375
- const anchorRemote = [...coverage.keys()].sort(
2376
- (a, b) => coverage.get(b) - coverage.get(a) || a.localeCompare(b)
2377
- )[0];
2378
- return {
2379
- anchorRemote,
2380
- tagPerMember: Object.fromEntries(
2381
- winners.filter((w) => w.providers.has(anchorRemote)).map((w) => [w.member.name, w.tag])
2382
- )
2383
- };
2384
- }
2385
- function classifyPoolConservative(members, anchor) {
2386
- const classification = /* @__PURE__ */ new Map();
2387
- for (const remote of remotesInPool(members)) {
2388
- let coverageForced = false;
2389
- let verdict = "follow";
2390
- for (const member of members) {
2391
- const version = versionForRemote(member, remote);
2392
- if (!version) continue;
2393
- const anchorTag = anchor.tagPerMember[member.name];
2394
- if (!anchorTag) {
2395
- coverageForced = true;
2396
- continue;
2397
- }
2398
- if (version.tag === anchorTag) continue;
2399
- if (version.action === "scope") {
2400
- verdict = "scope-incompat";
2401
- break;
2402
- }
2403
- }
2404
- classification.set(
2405
- remote,
2406
- verdict === "scope-incompat" ? "scope-incompat" : coverageForced ? "scope-coverage" : "follow"
2407
- );
2408
- }
2409
- classification.set(anchor.anchorRemote, "follow");
2410
- return classification;
2386
+ function islandedRemotes(members) {
2387
+ const islanded = /* @__PURE__ */ new Set();
2388
+ for (const member of members)
2389
+ for (const version of member.external.versions)
2390
+ if (version.action === "scope") for (const remote of version.remotes) islanded.add(remote.name);
2391
+ return islanded;
2411
2392
  }
2412
2393
  function createPoolSharedExternals(config, ports) {
2413
2394
  return () => {
@@ -2443,73 +2424,57 @@ function createPoolSharedExternals(config, ports) {
2443
2424
  if (members.length < 2) return;
2444
2425
  const allRemotes = remotesInPool(members);
2445
2426
  if (allRemotes.length < 2) return;
2446
- const anchor = selectCoverageAnchor(poolWinners(members));
2447
- if (!anchor) return;
2448
- const classification = classifyPoolConservative(members, anchor);
2427
+ const islanded = islandedRemotes(members);
2449
2428
  config.log.debug(
2450
2429
  3,
2451
- `[${scope}][pool:${poolName}] members=[${members.map((m) => m.name).join(", ")}], anchor=${anchor.anchorRemote}, remotes={${allRemotes.map((r) => `${r}:${classification.get(r)}`).join(", ")}}`
2430
+ `[${scope}][pool:${poolName}] ${members.length} members across ${allRemotes.length} remotes, islanded={${[...islanded].join(", ") || "\u2205"}}
2431
+ ` + members.map((m) => ` - ${m.name}`).join("\n")
2452
2432
  );
2453
- const incompatForced = allRemotes.some((r) => classification.get(r) === "scope-incompat");
2454
- if (incompatForced && config.strict.strictExternalCompatibility) {
2433
+ if (islanded.size > 0 && config.strict.strictExternalCompatibility) {
2455
2434
  config.log.error(
2456
2435
  3,
2457
- `[${scope}][pool:${poolName}] A remote is version-incompatible with anchor '${anchor.anchorRemote}'.`
2436
+ `[${scope}][pool:${poolName}] version-incompatible remotes cannot be pooled: {${[...islanded].join(", ")}}.`
2458
2437
  );
2459
2438
  throw new NFError(`Could not pool '${poolName}' in scope ${scope}.`);
2460
2439
  }
2461
2440
  for (const member of members) {
2462
- const rebuilt = rebuildMember(member, anchor, classification);
2441
+ const rebuilt = rebuildMember(member, islanded);
2463
2442
  warnIfScopedOnly(poolName, member.name, rebuilt, scope);
2464
2443
  ports.sharedExternalsRepo.addOrUpdate(member.name, rebuilt, scope);
2465
2444
  }
2466
2445
  }
2467
2446
  function warnIfScopedOnly(poolName, memberName, rebuilt, scope) {
2468
2447
  if (rebuilt.versions.some((v) => v.action === "share")) return;
2469
- const copies = rebuilt.versions.reduce((n, v) => n + v.remotes.length, 0);
2448
+ const consumers = new Set(rebuilt.versions.flatMap((v) => v.remotes.map((r) => r.name))).size;
2449
+ if (consumers < 2) return;
2470
2450
  config.log.warn(
2471
2451
  3,
2472
- `[${scope}][pool:${poolName}] '${memberName}' is scoped-only \u2014 no coherent shared build provides it; ${copies} remotes download their own copy.`
2452
+ `[${scope}][pool:${poolName}] '${memberName}' is scoped-only \u2014 no coherent shared build provides it; ${consumers} remotes download their own copy.`
2473
2453
  );
2474
2454
  }
2475
- function rebuildMember(member, anchor, classification) {
2476
- const anchorTag = anchor.tagPerMember[member.name];
2455
+ function rebuildMember(member, islanded) {
2477
2456
  const entries = member.external.versions.flatMap(
2478
- (v) => v.remotes.map((meta) => ({ remote: meta.name, tag: v.tag, host: v.host, meta }))
2479
- );
2480
- if (!anchorTag) {
2481
- return {
2482
- dirty: false,
2483
- versions: entries.length > 0 ? [
2484
- {
2485
- tag: entries[0].tag,
2486
- host: false,
2487
- action: "scope",
2488
- remotes: entries.map((e) => e.meta)
2489
- }
2490
- ] : []
2491
- };
2492
- }
2493
- const anchorMeta = entries.find(
2494
- (e) => e.remote === anchor.anchorRemote && e.tag === anchorTag
2495
- ).meta;
2496
- const anchorHost = member.external.versions.find((v) => v.tag === anchorTag)?.host ?? false;
2497
- const scopes = (e) => {
2498
- const cls = classification.get(e.remote);
2499
- return cls === "scope-incompat" || cls === "scope-coverage" && e.tag !== anchorTag;
2500
- };
2501
- const shareOnAnchor = entries.filter(
2502
- (e) => e.remote !== anchor.anchorRemote && e.tag === anchorTag && !scopes(e)
2457
+ (v) => v.remotes.map((meta) => ({ remote: meta.name, tag: v.tag, host: v.host, action: v.action, meta }))
2503
2458
  );
2504
- const shareVersion = {
2505
- tag: anchorTag,
2506
- host: anchorHost,
2507
- action: "share",
2508
- remotes: [anchorMeta, ...shareOnAnchor.map((e) => e.meta)]
2509
- };
2459
+ const isScoped = (e) => islanded.has(e.remote) || e.action === "scope";
2460
+ let scoped = entries.filter(isScoped);
2461
+ let clean = entries.filter((e) => !isScoped(e));
2462
+ if (!clean.some((e) => e.action === "share")) {
2463
+ scoped = [...scoped, ...clean];
2464
+ clean = [];
2465
+ }
2466
+ const shareEntries = clean.filter((e) => e.action === "share");
2467
+ const shareVersion = shareEntries.length > 0 ? [
2468
+ {
2469
+ tag: shareEntries[0].tag,
2470
+ host: shareEntries[0].host,
2471
+ action: "share",
2472
+ remotes: shareEntries.map((e) => e.meta)
2473
+ }
2474
+ ] : [];
2510
2475
  const skipByTag = /* @__PURE__ */ new Map();
2511
- for (const e of entries) {
2512
- if (e.tag === anchorTag || scopes(e)) continue;
2476
+ for (const e of clean) {
2477
+ if (e.action !== "skip") continue;
2513
2478
  const version = skipByTag.get(e.tag) ?? {
2514
2479
  tag: e.tag,
2515
2480
  host: e.host,
@@ -2519,16 +2484,25 @@ function createPoolSharedExternals(config, ports) {
2519
2484
  version.remotes.push(e.meta);
2520
2485
  skipByTag.set(e.tag, version);
2521
2486
  }
2522
- const scoped = entries.filter((e) => scopes(e));
2523
- const scopeVersion = scoped.length > 0 ? [{ tag: anchorTag, host: false, action: "scope", remotes: scoped.map((e) => e.meta) }] : [];
2487
+ const scopeByTag = /* @__PURE__ */ new Map();
2488
+ for (const e of scoped) {
2489
+ const version = scopeByTag.get(e.tag) ?? {
2490
+ tag: e.tag,
2491
+ host: false,
2492
+ action: "scope",
2493
+ remotes: []
2494
+ };
2495
+ version.remotes.push(e.meta);
2496
+ scopeByTag.set(e.tag, version);
2497
+ }
2524
2498
  return {
2525
2499
  dirty: false,
2526
- versions: [shareVersion, ...skipByTag.values(), ...scopeVersion]
2500
+ versions: [...shareVersion, ...skipByTag.values(), ...scopeByTag.values()]
2527
2501
  };
2528
2502
  }
2529
2503
  }
2530
2504
 
2531
- // node_modules/.pnpm/@softarc+native-federation@4.3.0_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
2505
+ // node_modules/.pnpm/@softarc+native-federation@4.3.2_typescript@6.0.3/node_modules/@softarc/native-federation/dist/lib/domain/core/chunk.js
2532
2506
  var CHUNK_PREFIX = "@nf-internal";
2533
2507
  function toChunkImport(fileName) {
2534
2508
  if (fileName.startsWith("./")) {
@@ -2635,6 +2609,17 @@ function createGenerateImportMap(config, ports) {
2635
2609
  for (const m of mappings) {
2636
2610
  addToScope(importMap, rScope, { [m.packageName]: m.url });
2637
2611
  }
2612
+ for (const [packageName, file] of Object.entries(r.entries)) {
2613
+ if (packageName in source.entries) continue;
2614
+ if (config.strict.strictEntryPointCoverage) {
2615
+ warnUncoveredEntrypoint(shareScope, externalName, r.name, packageName);
2616
+ continue;
2617
+ }
2618
+ const url = join(rScope, file);
2619
+ addToScope(importMap, rScope, { [packageName]: url });
2620
+ addIntegrity(importMap, url, r.name, file);
2621
+ r.cached = true;
2622
+ }
2638
2623
  });
2639
2624
  for (const m of mappings) {
2640
2625
  addIntegrity(importMap, m.url, m.name, m.file);
@@ -2702,10 +2687,35 @@ function createGenerateImportMap(config, ports) {
2702
2687
  registerBundleChunks(chunkBundles, version.remotes[0].name, version.remotes[0].bundle);
2703
2688
  version.remotes[0].cached = true;
2704
2689
  }
2690
+ for (const version of external.versions) {
2691
+ if (version.action !== "skip") continue;
2692
+ const remote = version.remotes[0];
2693
+ for (const [packageName, file] of Object.entries(remote.entries)) {
2694
+ if (importMap.imports[packageName]) continue;
2695
+ if (config.strict.strictEntryPointCoverage) {
2696
+ warnUncoveredEntrypoint(GLOBAL_SCOPE, externalName, remote.name, packageName);
2697
+ continue;
2698
+ }
2699
+ const scope = getScope2(GLOBAL_SCOPE, remote.name, externalName);
2700
+ const url = join(scope, file);
2701
+ addToGlobal(importMap, { [packageName]: url });
2702
+ addIntegrity(importMap, url, remote.name, file);
2703
+ registerBundleChunks(chunkBundles, remote.name, remote.bundle);
2704
+ remote.cached = true;
2705
+ }
2706
+ }
2705
2707
  ports.sharedExternalsRepo.addOrUpdate(externalName, external);
2706
2708
  }
2707
2709
  return importMap;
2708
2710
  }
2711
+ function warnUncoveredEntrypoint(scope, externalName, remoteName, packageName) {
2712
+ const msg = `[${scope}][${externalName}][${remoteName}] Entrypoint '${packageName}' is not covered by the shared version.`;
2713
+ if (config.strict.strictImportMap) {
2714
+ config.log.error(4, msg);
2715
+ throw new NFError("Could not create ImportMap.");
2716
+ }
2717
+ config.log.warn(4, msg);
2718
+ }
2709
2719
  function notifyDuplicateGlobalExternal(externalName) {
2710
2720
  if (config.strict.strictImportMap) {
2711
2721
  config.log.error(4, `[${externalName}] Shared external has multiple shared versions.`);
@@ -2928,6 +2938,9 @@ function createUpdateCache(config, ports) {
2928
2938
  }
2929
2939
  config.log.warn(8, errorMsg);
2930
2940
  }
2941
+ if (action === "skip" && config.strict.strictEntryPointCoverage && sharedVersion && Object.keys(remote.entries).some((e) => !(e in sharedVersion.remotes[0].entries))) {
2942
+ action = "scope";
2943
+ }
2931
2944
  const matchingVersion = cached2.versions.find((cached3) => cached3.tag === tag);
2932
2945
  if (matchingVersion) {
2933
2946
  assertSameVersionCompatibility(matchingVersion);
@@ -2992,8 +3005,6 @@ function createPoolDynamicExternals(config) {
2992
3005
  const memberActions = members.map((name) => actions[name].action);
2993
3006
  if (memberActions.includes("scope")) {
2994
3007
  members.forEach(scope);
2995
- } else if (memberActions.includes("share") && memberActions.includes("skip")) {
2996
- members.filter((name) => actions[name].action === "share").forEach(scope);
2997
3008
  }
2998
3009
  }
2999
3010
  }
@@ -3042,6 +3053,16 @@ function createConvertToImportMap(config, ports) {
3042
3053
  Object.entries(override).forEach(([packageName, url]) => {
3043
3054
  addToScopes(remoteEntryScope, packageName, url, importMap);
3044
3055
  });
3056
+ Object.entries(external.entries).forEach(([packageName, fileName]) => {
3057
+ if (packageName in override) return;
3058
+ if (config.strict.strictEntryPointCoverage) {
3059
+ warnUncoveredEntrypoint(remoteEntry.name, external.packageName, packageName);
3060
+ return;
3061
+ }
3062
+ const url = join(remoteEntryScope, fileName);
3063
+ addToScopes(remoteEntryScope, packageName, url, importMap);
3064
+ addIntegrity(importMap, url, integrityMap, fileName);
3065
+ });
3045
3066
  return;
3046
3067
  }
3047
3068
  log.error(
@@ -3108,6 +3129,14 @@ function createConvertToImportMap(config, ports) {
3108
3129
  });
3109
3130
  return importMap;
3110
3131
  }
3132
+ function warnUncoveredEntrypoint(remoteName, externalName, packageName) {
3133
+ const msg = `[${remoteName}][${externalName}] Entrypoint '${packageName}' is not covered by the override.`;
3134
+ if (config.strict.strictImportMap) {
3135
+ log.error(9, msg);
3136
+ throw new NFError("Could not create ImportMap.");
3137
+ }
3138
+ log.warn(9, msg);
3139
+ }
3111
3140
  function addIntegrity(importMap, url, integrityMap, file) {
3112
3141
  const hash = integrityMap?.[file];
3113
3142
  if (!hash) return;