everything-dev 1.7.1 → 1.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 (136) hide show
  1. package/dist/api.cjs +1 -1
  2. package/dist/api.mjs +1 -1
  3. package/dist/app.cjs +82 -51
  4. package/dist/app.cjs.map +1 -1
  5. package/dist/app.mjs +82 -51
  6. package/dist/app.mjs.map +1 -1
  7. package/dist/cli/upgrade.cjs +70 -10
  8. package/dist/cli/upgrade.cjs.map +1 -1
  9. package/dist/cli/upgrade.mjs +71 -11
  10. package/dist/cli/upgrade.mjs.map +1 -1
  11. package/dist/components/dev-view.cjs +6 -3
  12. package/dist/components/dev-view.cjs.map +1 -1
  13. package/dist/components/dev-view.mjs +6 -3
  14. package/dist/components/dev-view.mjs.map +1 -1
  15. package/dist/components/streaming-view.cjs +5 -2
  16. package/dist/components/streaming-view.cjs.map +1 -1
  17. package/dist/components/streaming-view.mjs +5 -2
  18. package/dist/components/streaming-view.mjs.map +1 -1
  19. package/dist/config.cjs +28 -5
  20. package/dist/config.cjs.map +1 -1
  21. package/dist/config.d.cts.map +1 -1
  22. package/dist/config.d.mts.map +1 -1
  23. package/dist/config.mjs +28 -5
  24. package/dist/config.mjs.map +1 -1
  25. package/dist/contract.cjs +1 -0
  26. package/dist/contract.cjs.map +1 -1
  27. package/dist/contract.d.cts +14 -6
  28. package/dist/contract.d.cts.map +1 -1
  29. package/dist/contract.d.mts +14 -6
  30. package/dist/contract.d.mts.map +1 -1
  31. package/dist/contract.mjs +1 -0
  32. package/dist/contract.mjs.map +1 -1
  33. package/dist/dev-logs.cjs +6 -2
  34. package/dist/dev-logs.cjs.map +1 -1
  35. package/dist/dev-logs.mjs +7 -2
  36. package/dist/dev-logs.mjs.map +1 -1
  37. package/dist/dev-session.cjs +27 -23
  38. package/dist/dev-session.cjs.map +1 -1
  39. package/dist/dev-session.mjs +27 -24
  40. package/dist/dev-session.mjs.map +1 -1
  41. package/dist/federation.server.cjs +1 -1
  42. package/dist/federation.server.mjs +1 -1
  43. package/dist/host.cjs +4 -3
  44. package/dist/host.cjs.map +1 -1
  45. package/dist/host.d.cts.map +1 -1
  46. package/dist/host.d.mts.map +1 -1
  47. package/dist/host.mjs +4 -3
  48. package/dist/host.mjs.map +1 -1
  49. package/dist/integrity.cjs +68 -2
  50. package/dist/integrity.cjs.map +1 -1
  51. package/dist/integrity.d.cts +14 -1
  52. package/dist/integrity.d.cts.map +1 -1
  53. package/dist/integrity.d.mts +14 -1
  54. package/dist/integrity.d.mts.map +1 -1
  55. package/dist/integrity.mjs +66 -3
  56. package/dist/integrity.mjs.map +1 -1
  57. package/dist/mf.cjs +32 -0
  58. package/dist/mf.cjs.map +1 -1
  59. package/dist/mf.d.cts +3 -1
  60. package/dist/mf.d.cts.map +1 -1
  61. package/dist/mf.d.mts +3 -1
  62. package/dist/mf.d.mts.map +1 -1
  63. package/dist/mf.mjs +32 -1
  64. package/dist/mf.mjs.map +1 -1
  65. package/dist/orchestrator.cjs +167 -317
  66. package/dist/orchestrator.cjs.map +1 -1
  67. package/dist/orchestrator.d.cts +24 -21
  68. package/dist/orchestrator.d.cts.map +1 -1
  69. package/dist/orchestrator.d.mts +24 -21
  70. package/dist/orchestrator.d.mts.map +1 -1
  71. package/dist/orchestrator.mjs +168 -316
  72. package/dist/orchestrator.mjs.map +1 -1
  73. package/dist/plugin.cjs +38 -107
  74. package/dist/plugin.cjs.map +1 -1
  75. package/dist/plugin.d.cts +19 -5
  76. package/dist/plugin.d.cts.map +1 -1
  77. package/dist/plugin.d.mts +19 -5
  78. package/dist/plugin.d.mts.map +1 -1
  79. package/dist/plugin.mjs +39 -108
  80. package/dist/plugin.mjs.map +1 -1
  81. package/dist/service-descriptor.cjs +188 -0
  82. package/dist/service-descriptor.cjs.map +1 -0
  83. package/dist/service-descriptor.d.cts +107 -0
  84. package/dist/service-descriptor.d.cts.map +1 -0
  85. package/dist/service-descriptor.d.mts +107 -0
  86. package/dist/service-descriptor.d.mts.map +1 -0
  87. package/dist/service-descriptor.mjs +182 -0
  88. package/dist/service-descriptor.mjs.map +1 -0
  89. package/dist/types.cjs +8 -1
  90. package/dist/types.cjs.map +1 -1
  91. package/dist/types.d.cts +18 -3
  92. package/dist/types.d.cts.map +1 -1
  93. package/dist/types.d.mts +18 -3
  94. package/dist/types.d.mts.map +1 -1
  95. package/dist/types.mjs +8 -1
  96. package/dist/types.mjs.map +1 -1
  97. package/dist/ui/index.cjs +1 -0
  98. package/dist/ui/index.d.cts +2 -2
  99. package/dist/ui/index.d.mts +2 -2
  100. package/dist/ui/index.mjs +2 -2
  101. package/dist/ui/runtime.cjs +4 -0
  102. package/dist/ui/runtime.cjs.map +1 -1
  103. package/dist/ui/runtime.d.cts +2 -1
  104. package/dist/ui/runtime.d.cts.map +1 -1
  105. package/dist/ui/runtime.d.mts +2 -1
  106. package/dist/ui/runtime.d.mts.map +1 -1
  107. package/dist/ui/runtime.mjs +4 -1
  108. package/dist/ui/runtime.mjs.map +1 -1
  109. package/package.json +12 -4
  110. package/skills/dev-workflow/SKILL.md +105 -0
  111. package/skills/publish-sync/SKILL.md +130 -0
  112. package/src/app.ts +98 -204
  113. package/src/cli/upgrade.ts +113 -13
  114. package/src/components/dev-view.tsx +8 -3
  115. package/src/components/streaming-view.ts +7 -2
  116. package/src/config.ts +40 -8
  117. package/src/contract.ts +1 -0
  118. package/src/dev-logs.ts +8 -1
  119. package/src/dev-session.ts +56 -79
  120. package/src/host.ts +4 -3
  121. package/src/integrity.ts +96 -10
  122. package/src/mf.ts +42 -0
  123. package/src/orchestrator.ts +232 -411
  124. package/src/plugin.ts +48 -136
  125. package/src/service-descriptor.ts +258 -0
  126. package/src/types.ts +8 -1
  127. package/src/ui/runtime.ts +5 -0
  128. package/dist/process-registry.cjs +0 -120
  129. package/dist/process-registry.cjs.map +0 -1
  130. package/dist/process-registry.d.cts +0 -25
  131. package/dist/process-registry.d.cts.map +0 -1
  132. package/dist/process-registry.d.mts +0 -25
  133. package/dist/process-registry.d.mts.map +0 -1
  134. package/dist/process-registry.mjs +0 -119
  135. package/dist/process-registry.mjs.map +0 -1
  136. package/src/process-registry.ts +0 -154
@@ -1,3 +1,4 @@
1
+ import { fetchBosConfigFromFastKv } from "./fastkv.mjs";
1
2
  import { createHash } from "node:crypto";
2
3
 
3
4
  //#region src/integrity.ts
@@ -6,7 +7,7 @@ function computeSriHash(content) {
6
7
  }
7
8
  async function computeSriHashForUrl(url) {
8
9
  try {
9
- const entryUrl = url.endsWith("/remoteEntry.js") ? url : url.endsWith("/mf-manifest.json") ? `${url.replace(/\/mf-manifest\.json$/, "")}/remoteEntry.js` : `${url.replace(/\/$/, "")}/remoteEntry.js`;
10
+ const entryUrl = resolveEntryUrl(url);
10
11
  const response = await fetch(entryUrl);
11
12
  if (!response.ok) {
12
13
  console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);
@@ -18,8 +19,13 @@ async function computeSriHashForUrl(url) {
18
19
  return null;
19
20
  }
20
21
  }
22
+ function resolveEntryUrl(url) {
23
+ if (url.endsWith("/remoteEntry.js")) return url;
24
+ if (url.endsWith("/mf-manifest.json")) return `${url.replace(/\/mf-manifest\.json$/, "")}/remoteEntry.js`;
25
+ return `${url.replace(/\/$/, "")}/remoteEntry.js`;
26
+ }
21
27
  async function verifySriForUrl(url, expectedIntegrity) {
22
- const entryUrl = url.endsWith("/remoteEntry.js") ? url : url.endsWith("/mf-manifest.json") ? `${url.replace(/\/mf-manifest\.json$/, "")}/remoteEntry.js` : `${url.replace(/\/$/, "")}/remoteEntry.js`;
28
+ const entryUrl = resolveEntryUrl(url);
23
29
  const response = await fetch(entryUrl);
24
30
  if (!response.ok) {
25
31
  console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);
@@ -29,7 +35,64 @@ async function verifySriForUrl(url, expectedIntegrity) {
29
35
  if (computed !== expectedIntegrity) throw new Error(`[SRI] Integrity check failed for ${entryUrl}\n Expected: ${expectedIntegrity}\n Computed: ${computed}`);
30
36
  console.log(`[SRI] Integrity verified for ${entryUrl}`);
31
37
  }
38
+ var IntegrityRegistry = class {
39
+ hashes = /* @__PURE__ */ new Map();
40
+ register(url, integrity) {
41
+ this.hashes.set(url, integrity);
42
+ }
43
+ registerEntry(baseUrl, integrity) {
44
+ this.hashes.set(resolveEntryUrl(baseUrl), integrity);
45
+ }
46
+ get(url) {
47
+ return this.hashes.get(url);
48
+ }
49
+ has(url) {
50
+ return this.hashes.has(url);
51
+ }
52
+ entries() {
53
+ return this.hashes.entries();
54
+ }
55
+ };
56
+ function extractIntegrityHashes(config) {
57
+ const hashes = /* @__PURE__ */ new Map();
58
+ const app = config.app;
59
+ const plugins = config.plugins;
60
+ if (app) {
61
+ for (const [, entry] of Object.entries(app)) if (entry?.integrity && entry?.production) hashes.set(resolveEntryUrl(entry.production), entry.integrity);
62
+ }
63
+ if (plugins) {
64
+ for (const [, entry] of Object.entries(plugins)) if (entry?.integrity && entry?.production) hashes.set(resolveEntryUrl(entry.production), entry.integrity);
65
+ }
66
+ return hashes;
67
+ }
68
+ async function verifyConfigAgainstChain(localConfig, bosUrl) {
69
+ const mismatches = [];
70
+ let chainConfig;
71
+ try {
72
+ chainConfig = await fetchBosConfigFromFastKv(bosUrl);
73
+ } catch (error) {
74
+ console.warn(`[Attestation] Failed to fetch on-chain config: ${error instanceof Error ? error.message : String(error)}`);
75
+ return {
76
+ verified: false,
77
+ mismatches: ["chain-fetch-failed"]
78
+ };
79
+ }
80
+ const localHashes = extractIntegrityHashes(localConfig);
81
+ const chainHashes = extractIntegrityHashes(chainConfig);
82
+ for (const [url, chainHash] of chainHashes) {
83
+ const localHash = localHashes.get(url);
84
+ if (localHash && localHash !== chainHash) {
85
+ mismatches.push(url);
86
+ console.error(`[Attestation] Integrity mismatch for ${url}\n Local: ${localHash}\n Chain: ${chainHash}`);
87
+ }
88
+ }
89
+ if (mismatches.length === 0 && localHashes.size > 0) console.log(`[Attestation] Local config verified against on-chain anchor (${localHashes.size} entries checked)`);
90
+ return {
91
+ verified: mismatches.length === 0,
92
+ mismatches
93
+ };
94
+ }
32
95
 
33
96
  //#endregion
34
- export { computeSriHash, computeSriHashForUrl, verifySriForUrl };
97
+ export { IntegrityRegistry, computeSriHash, computeSriHashForUrl, resolveEntryUrl, verifyConfigAgainstChain, verifySriForUrl };
35
98
  //# sourceMappingURL=integrity.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"integrity.mjs","names":[],"sources":["../src/integrity.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\n\nexport function computeSriHash(content: string | Buffer): string {\n return `sha384-${createHash(\"sha384\").update(content).digest(\"base64\")}`;\n}\n\nexport async function computeSriHashForUrl(url: string): Promise<string | null> {\n try {\n const entryUrl = url.endsWith(\"/remoteEntry.js\")\n ? url\n : url.endsWith(\"/mf-manifest.json\")\n ? `${url.replace(/\\/mf-manifest\\.json$/, \"\")}/remoteEntry.js`\n : `${url.replace(/\\/$/, \"\")}/remoteEntry.js`;\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);\n return null;\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n return computeSriHash(buffer);\n } catch (error) {\n console.warn(\n `[SRI] Error computing integrity for ${url}:`,\n error instanceof Error ? error.message : error,\n );\n return null;\n }\n}\n\nexport async function verifySriForUrl(url: string, expectedIntegrity: string): Promise<void> {\n const entryUrl = url.endsWith(\"/remoteEntry.js\")\n ? url\n : url.endsWith(\"/mf-manifest.json\")\n ? `${url.replace(/\\/mf-manifest\\.json$/, \"\")}/remoteEntry.js`\n : `${url.replace(/\\/$/, \"\")}/remoteEntry.js`;\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);\n return;\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n const computed = computeSriHash(buffer);\n\n if (computed !== expectedIntegrity) {\n throw new Error(\n `[SRI] Integrity check failed for ${entryUrl}\\n Expected: ${expectedIntegrity}\\n Computed: ${computed}`,\n );\n }\n\n console.log(`[SRI] Integrity verified for ${entryUrl}`);\n}\n"],"mappings":";;;AAEA,SAAgB,eAAe,SAAkC;AAC/D,QAAO,UAAU,WAAW,SAAS,CAAC,OAAO,QAAQ,CAAC,OAAO,SAAS;;AAGxE,eAAsB,qBAAqB,KAAqC;AAC9E,KAAI;EACF,MAAM,WAAW,IAAI,SAAS,kBAAkB,GAC5C,MACA,IAAI,SAAS,oBAAoB,GAC/B,GAAG,IAAI,QAAQ,wBAAwB,GAAG,CAAC,mBAC3C,GAAG,IAAI,QAAQ,OAAO,GAAG,CAAC;EAEhC,MAAM,WAAW,MAAM,MAAM,SAAS;AACtC,MAAI,CAAC,SAAS,IAAI;AAChB,WAAQ,KAAK,yBAAyB,SAAS,IAAI,SAAS,OAAO,GAAG,SAAS,aAAa;AAC5F,UAAO;;AAGT,SAAO,eADQ,OAAO,KAAK,MAAM,SAAS,aAAa,CAAC,CAC3B;UACtB,OAAO;AACd,UAAQ,KACN,uCAAuC,IAAI,IAC3C,iBAAiB,QAAQ,MAAM,UAAU,MAC1C;AACD,SAAO;;;AAIX,eAAsB,gBAAgB,KAAa,mBAA0C;CAC3F,MAAM,WAAW,IAAI,SAAS,kBAAkB,GAC5C,MACA,IAAI,SAAS,oBAAoB,GAC/B,GAAG,IAAI,QAAQ,wBAAwB,GAAG,CAAC,mBAC3C,GAAG,IAAI,QAAQ,OAAO,GAAG,CAAC;CAEhC,MAAM,WAAW,MAAM,MAAM,SAAS;AACtC,KAAI,CAAC,SAAS,IAAI;AAChB,UAAQ,KAAK,yBAAyB,SAAS,qBAAqB,SAAS,SAAS;AACtF;;CAIF,MAAM,WAAW,eADF,OAAO,KAAK,MAAM,SAAS,aAAa,CAAC,CACjB;AAEvC,KAAI,aAAa,kBACf,OAAM,IAAI,MACR,oCAAoC,SAAS,gBAAgB,kBAAkB,gBAAgB,WAChG;AAGH,SAAQ,IAAI,gCAAgC,WAAW"}
1
+ {"version":3,"file":"integrity.mjs","names":[],"sources":["../src/integrity.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport { fetchBosConfigFromFastKv } from \"./fastkv\";\n\nexport function computeSriHash(content: string | Buffer): string {\n return `sha384-${createHash(\"sha384\").update(content).digest(\"base64\")}`;\n}\n\nexport async function computeSriHashForUrl(url: string): Promise<string | null> {\n try {\n const entryUrl = resolveEntryUrl(url);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl}: ${response.status} ${response.statusText}`);\n return null;\n }\n const buffer = Buffer.from(await response.arrayBuffer());\n return computeSriHash(buffer);\n } catch (error) {\n console.warn(\n `[SRI] Error computing integrity for ${url}:`,\n error instanceof Error ? error.message : error,\n );\n return null;\n }\n}\n\nexport function resolveEntryUrl(url: string): string {\n if (url.endsWith(\"/remoteEntry.js\")) return url;\n if (url.endsWith(\"/mf-manifest.json\"))\n return `${url.replace(/\\/mf-manifest\\.json$/, \"\")}/remoteEntry.js`;\n return `${url.replace(/\\/$/, \"\")}/remoteEntry.js`;\n}\n\nexport async function verifySriForUrl(url: string, expectedIntegrity: string): Promise<void> {\n const entryUrl = resolveEntryUrl(url);\n\n const response = await fetch(entryUrl);\n if (!response.ok) {\n console.warn(`[SRI] Failed to fetch ${entryUrl} for verification: ${response.status}`);\n return;\n }\n\n const buffer = Buffer.from(await response.arrayBuffer());\n const computed = computeSriHash(buffer);\n\n if (computed !== expectedIntegrity) {\n throw new Error(\n `[SRI] Integrity check failed for ${entryUrl}\\n Expected: ${expectedIntegrity}\\n Computed: ${computed}`,\n );\n }\n\n console.log(`[SRI] Integrity verified for ${entryUrl}`);\n}\n\nexport class IntegrityRegistry {\n private hashes = new Map<string, string>();\n\n register(url: string, integrity: string): void {\n this.hashes.set(url, integrity);\n }\n\n registerEntry(baseUrl: string, integrity: string): void {\n this.hashes.set(resolveEntryUrl(baseUrl), integrity);\n }\n\n get(url: string): string | undefined {\n return this.hashes.get(url);\n }\n\n has(url: string): boolean {\n return this.hashes.has(url);\n }\n\n entries(): IterableIterator<[string, string]> {\n return this.hashes.entries();\n }\n}\n\nfunction extractIntegrityHashes(config: Record<string, unknown>): Map<string, string> {\n const hashes = new Map<string, string>();\n const app = config.app as Record<string, Record<string, unknown>> | undefined;\n const plugins = config.plugins as Record<string, Record<string, unknown>> | undefined;\n\n if (app) {\n for (const [, entry] of Object.entries(app)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n if (plugins) {\n for (const [, entry] of Object.entries(plugins)) {\n if (entry?.integrity && entry?.production) {\n hashes.set(resolveEntryUrl(entry.production as string), entry.integrity as string);\n }\n }\n }\n\n return hashes;\n}\n\nexport async function verifyConfigAgainstChain(\n localConfig: Record<string, unknown>,\n bosUrl: string,\n): Promise<{ verified: boolean; mismatches: string[] }> {\n const mismatches: string[] = [];\n\n let chainConfig: Record<string, unknown>;\n try {\n chainConfig = await fetchBosConfigFromFastKv<Record<string, unknown>>(bosUrl);\n } catch (error) {\n console.warn(\n `[Attestation] Failed to fetch on-chain config: ${error instanceof Error ? error.message : String(error)}`,\n );\n return { verified: false, mismatches: [\"chain-fetch-failed\"] };\n }\n\n const localHashes = extractIntegrityHashes(localConfig);\n const chainHashes = extractIntegrityHashes(chainConfig);\n\n for (const [url, chainHash] of chainHashes) {\n const localHash = localHashes.get(url);\n if (localHash && localHash !== chainHash) {\n mismatches.push(url);\n console.error(\n `[Attestation] Integrity mismatch for ${url}\\n Local: ${localHash}\\n Chain: ${chainHash}`,\n );\n }\n }\n\n if (mismatches.length === 0 && localHashes.size > 0) {\n console.log(\n `[Attestation] Local config verified against on-chain anchor (${localHashes.size} entries checked)`,\n );\n }\n\n return { verified: mismatches.length === 0, mismatches };\n}\n"],"mappings":"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"}
package/dist/mf.cjs CHANGED
@@ -1,5 +1,6 @@
1
1
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
2
2
  const require_runtime = require('./_virtual/_rolldown/runtime.cjs');
3
+ const require_integrity = require('./integrity.cjs');
3
4
  let _module_federation_enhanced_runtime = require("@module-federation/enhanced/runtime");
4
5
  let _module_federation_runtime_core = require("@module-federation/runtime-core");
5
6
 
@@ -18,6 +19,36 @@ function patchManifestFetchForSsrPublicPath(mf) {
18
19
  });
19
20
  });
20
21
  }
22
+ function installIntegrityFetchHook(mf, registry) {
23
+ if (!mf || !mf.loaderHook?.lifecycle?.fetch?.on) {
24
+ console.warn("[SRI] MF lifecycle fetch hook not available, skipping integrity-in-pipeline");
25
+ return;
26
+ }
27
+ if (mf.__everythingDevIntegrityHook === true) return;
28
+ mf.__everythingDevIntegrityHook = true;
29
+ mf.loaderHook.lifecycle.fetch.on((url, init) => {
30
+ if (typeof url !== "string") return;
31
+ const expectedHash = registry.get(url);
32
+ if (!expectedHash) return;
33
+ return fetch(url, init).then(async (res) => {
34
+ const buffer = Buffer.from(await res.arrayBuffer());
35
+ const computed = require_integrity.computeSriHash(buffer);
36
+ if (computed !== expectedHash) {
37
+ console.error(`[SRI] Integrity check failed in MF fetch pipeline for ${url}\n Expected: ${expectedHash}\n Computed: ${computed}`);
38
+ return new Response(`Integrity check failed for ${url}`, {
39
+ status: 500,
40
+ statusText: "Integrity Check Failed"
41
+ });
42
+ }
43
+ console.log(`[SRI] Integrity verified in pipeline for ${url}`);
44
+ return new Response(buffer, {
45
+ status: res.status,
46
+ statusText: res.statusText,
47
+ headers: res.headers
48
+ });
49
+ });
50
+ });
51
+ }
21
52
  function getFederationInstance() {
22
53
  if (mfInstance) return mfInstance;
23
54
  const existing = (0, _module_federation_enhanced_runtime.getInstance)();
@@ -71,6 +102,7 @@ async function ensureNodeRuntimePlugin() {
71
102
  //#endregion
72
103
  exports.ensureNodeRuntimePlugin = ensureNodeRuntimePlugin;
73
104
  exports.getFederationInstance = getFederationInstance;
105
+ exports.installIntegrityFetchHook = installIntegrityFetchHook;
74
106
  exports.loadRemoteModule = loadRemoteModule;
75
107
  exports.patchManifestFetchForSsrPublicPath = patchManifestFetchForSsrPublicPath;
76
108
  exports.registerRemote = registerRemote;
package/dist/mf.cjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mf.cjs","names":[],"sources":["../src/mf.ts"],"sourcesContent":["import { createInstance, getInstance } from \"@module-federation/enhanced/runtime\";\nimport { setGlobalFederationInstance } from \"@module-federation/runtime-core\";\n\ntype FederationInstance = ReturnType<typeof createInstance>;\n\nlet mfInstance: FederationInstance | null = null;\n\nexport function patchManifestFetchForSsrPublicPath(mf: FederationInstance): void {\n if (!mf || !(mf as any).loaderHook?.lifecycle?.fetch?.on) return;\n if ((mf as any).__everythingDevPatchedManifestFetch === true) return;\n (mf as any).__everythingDevPatchedManifestFetch = true;\n\n (mf as any).loaderHook.lifecycle.fetch.on((url: unknown, init: unknown) => {\n if (typeof url !== \"string\" || !url.endsWith(\"/mf-manifest.json\")) {\n return;\n }\n return fetch(url, init as any)\n .then((res) => res.json())\n .then((json: any) => {\n json.metaData = json.metaData ?? {};\n json.metaData.ssrPublicPath =\n json.metaData.ssrPublicPath ?? url.replace(/\\/mf-manifest\\.json$/, \"/\");\n return new Response(JSON.stringify(json), {\n headers: { \"content-type\": \"application/json\" },\n });\n });\n });\n}\n\nexport function getFederationInstance(): FederationInstance {\n if (mfInstance) return mfInstance;\n\n const existing = getInstance();\n if (existing) {\n mfInstance = existing as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n }\n\n mfInstance = createInstance({\n name: \"host\",\n remotes: [],\n }) as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n}\n\nexport async function registerRemote(opts: {\n name: string;\n entry: string;\n type?: \"manifest\" | \"script\";\n}): Promise<void> {\n const instance = getFederationInstance();\n\n const inferType = (): \"manifest\" | \"script\" => {\n if (opts.type) return opts.type;\n if (opts.entry.endsWith(\"/mf-manifest.json\")) return \"manifest\";\n if (opts.entry.endsWith(\"/remoteEntry.js\")) return \"script\";\n return typeof window === \"undefined\" ? \"script\" : \"manifest\";\n };\n\n const remoteType = inferType();\n\n instance.registerRemotes([\n {\n name: opts.name,\n entry: opts.entry,\n type: remoteType,\n },\n ]);\n}\n\nexport async function loadRemoteModule<T>(\n specifier: string,\n options?: { loadFactory?: boolean; from?: \"build\" | \"runtime\" },\n): Promise<T> {\n const instance = getFederationInstance();\n\n const isServer = typeof window === \"undefined\";\n if (isServer) {\n await (instance as any).initializeSharing?.(\"default\");\n }\n\n const mod = await instance.loadRemote<T>(specifier, options as any);\n if (!mod) {\n throw new Error(`Failed to load remote module: ${specifier}`);\n }\n return mod;\n}\n\nexport async function ensureNodeRuntimePlugin(): Promise<void> {\n const instance = getFederationInstance();\n if (typeof window !== \"undefined\") return;\n if ((instance as any).__nodeRuntimePluginLoaded) return;\n\n const mod: any = await import(\"@module-federation/node/runtimePlugin\");\n const factory = mod?.default ?? mod;\n const plugin = typeof factory === \"function\" ? factory() : null;\n if (plugin) {\n instance.registerPlugins([plugin]);\n }\n (instance as any).__nodeRuntimePluginLoaded = true;\n}\n"],"mappings":";;;;;;AAKA,IAAI,aAAwC;AAE5C,SAAgB,mCAAmC,IAA8B;AAC/E,KAAI,CAAC,MAAM,CAAE,GAAW,YAAY,WAAW,OAAO,GAAI;AAC1D,KAAK,GAAW,wCAAwC,KAAM;AAC9D,CAAC,GAAW,sCAAsC;AAElD,CAAC,GAAW,WAAW,UAAU,MAAM,IAAI,KAAc,SAAkB;AACzE,MAAI,OAAO,QAAQ,YAAY,CAAC,IAAI,SAAS,oBAAoB,CAC/D;AAEF,SAAO,MAAM,KAAK,KAAY,CAC3B,MAAM,QAAQ,IAAI,MAAM,CAAC,CACzB,MAAM,SAAc;AACnB,QAAK,WAAW,KAAK,YAAY,EAAE;AACnC,QAAK,SAAS,gBACZ,KAAK,SAAS,iBAAiB,IAAI,QAAQ,wBAAwB,IAAI;AACzE,UAAO,IAAI,SAAS,KAAK,UAAU,KAAK,EAAE,EACxC,SAAS,EAAE,gBAAgB,oBAAoB,EAChD,CAAC;IACF;GACJ;;AAGJ,SAAgB,wBAA4C;AAC1D,KAAI,WAAY,QAAO;CAEvB,MAAM,iEAAwB;AAC9B,KAAI,UAAU;AACZ,eAAa;AACb,mEAA4B,WAAkB;AAC9C,qCAAmC,WAAW;AAC9C,SAAO;;AAGT,sEAA4B;EAC1B,MAAM;EACN,SAAS,EAAE;EACZ,CAAC;AACF,kEAA4B,WAAkB;AAC9C,oCAAmC,WAAW;AAC9C,QAAO;;AAGT,eAAsB,eAAe,MAInB;CAChB,MAAM,WAAW,uBAAuB;CAExC,MAAM,kBAAyC;AAC7C,MAAI,KAAK,KAAM,QAAO,KAAK;AAC3B,MAAI,KAAK,MAAM,SAAS,oBAAoB,CAAE,QAAO;AACrD,MAAI,KAAK,MAAM,SAAS,kBAAkB,CAAE,QAAO;AACnD,SAAO,OAAO,WAAW,cAAc,WAAW;;CAGpD,MAAM,aAAa,WAAW;AAE9B,UAAS,gBAAgB,CACvB;EACE,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,MAAM;EACP,CACF,CAAC;;AAGJ,eAAsB,iBACpB,WACA,SACY;CACZ,MAAM,WAAW,uBAAuB;AAGxC,KADiB,OAAO,WAAW,YAEjC,OAAO,SAAiB,oBAAoB,UAAU;CAGxD,MAAM,MAAM,MAAM,SAAS,WAAc,WAAW,QAAe;AACnE,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,iCAAiC,YAAY;AAE/D,QAAO;;AAGT,eAAsB,0BAAyC;CAC7D,MAAM,WAAW,uBAAuB;AACxC,KAAI,OAAO,WAAW,YAAa;AACnC,KAAK,SAAiB,0BAA2B;CAEjD,MAAM,MAAW,MAAM,OAAO;CAC9B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,SAAS,OAAO,YAAY,aAAa,SAAS,GAAG;AAC3D,KAAI,OACF,UAAS,gBAAgB,CAAC,OAAO,CAAC;AAEpC,CAAC,SAAiB,4BAA4B"}
1
+ {"version":3,"file":"mf.cjs","names":["computeSriHash"],"sources":["../src/mf.ts"],"sourcesContent":["import { createInstance, getInstance } from \"@module-federation/enhanced/runtime\";\nimport { setGlobalFederationInstance } from \"@module-federation/runtime-core\";\nimport { computeSriHash, type IntegrityRegistry } from \"./integrity\";\n\ntype FederationInstance = ReturnType<typeof createInstance>;\n\nlet mfInstance: FederationInstance | null = null;\n\nexport function patchManifestFetchForSsrPublicPath(mf: FederationInstance): void {\n if (!mf || !(mf as any).loaderHook?.lifecycle?.fetch?.on) return;\n if ((mf as any).__everythingDevPatchedManifestFetch === true) return;\n (mf as any).__everythingDevPatchedManifestFetch = true;\n\n (mf as any).loaderHook.lifecycle.fetch.on((url: unknown, init: unknown) => {\n if (typeof url !== \"string\" || !url.endsWith(\"/mf-manifest.json\")) {\n return;\n }\n return fetch(url, init as any)\n .then((res) => res.json())\n .then((json: any) => {\n json.metaData = json.metaData ?? {};\n json.metaData.ssrPublicPath =\n json.metaData.ssrPublicPath ?? url.replace(/\\/mf-manifest\\.json$/, \"/\");\n return new Response(JSON.stringify(json), {\n headers: { \"content-type\": \"application/json\" },\n });\n });\n });\n}\n\nexport function installIntegrityFetchHook(\n mf: FederationInstance,\n registry: IntegrityRegistry,\n): void {\n if (!mf || !(mf as any).loaderHook?.lifecycle?.fetch?.on) {\n console.warn(\"[SRI] MF lifecycle fetch hook not available, skipping integrity-in-pipeline\");\n return;\n }\n if ((mf as any).__everythingDevIntegrityHook === true) return;\n (mf as any).__everythingDevIntegrityHook = true;\n\n (mf as any).loaderHook.lifecycle.fetch.on((url: unknown, init: unknown) => {\n if (typeof url !== \"string\") return;\n\n const expectedHash = registry.get(url);\n if (!expectedHash) return;\n\n return fetch(url, init as any).then(async (res) => {\n const buffer = Buffer.from(await res.arrayBuffer());\n const computed = computeSriHash(buffer);\n\n if (computed !== expectedHash) {\n console.error(\n `[SRI] Integrity check failed in MF fetch pipeline for ${url}\\n Expected: ${expectedHash}\\n Computed: ${computed}`,\n );\n return new Response(`Integrity check failed for ${url}`, {\n status: 500,\n statusText: \"Integrity Check Failed\",\n });\n }\n\n console.log(`[SRI] Integrity verified in pipeline for ${url}`);\n return new Response(buffer, {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n });\n });\n}\n\nexport function getFederationInstance(): FederationInstance {\n if (mfInstance) return mfInstance;\n\n const existing = getInstance();\n if (existing) {\n mfInstance = existing as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n }\n\n mfInstance = createInstance({\n name: \"host\",\n remotes: [],\n }) as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n}\n\nexport async function registerRemote(opts: {\n name: string;\n entry: string;\n type?: \"manifest\" | \"script\";\n}): Promise<void> {\n const instance = getFederationInstance();\n\n const inferType = (): \"manifest\" | \"script\" => {\n if (opts.type) return opts.type;\n if (opts.entry.endsWith(\"/mf-manifest.json\")) return \"manifest\";\n if (opts.entry.endsWith(\"/remoteEntry.js\")) return \"script\";\n return typeof window === \"undefined\" ? \"script\" : \"manifest\";\n };\n\n const remoteType = inferType();\n\n instance.registerRemotes([\n {\n name: opts.name,\n entry: opts.entry,\n type: remoteType,\n },\n ]);\n}\n\nexport async function loadRemoteModule<T>(\n specifier: string,\n options?: { loadFactory?: boolean; from?: \"build\" | \"runtime\" },\n): Promise<T> {\n const instance = getFederationInstance();\n\n const isServer = typeof window === \"undefined\";\n if (isServer) {\n await (instance as any).initializeSharing?.(\"default\");\n }\n\n const mod = await instance.loadRemote<T>(specifier, options as any);\n if (!mod) {\n throw new Error(`Failed to load remote module: ${specifier}`);\n }\n return mod;\n}\n\nexport async function ensureNodeRuntimePlugin(): Promise<void> {\n const instance = getFederationInstance();\n if (typeof window !== \"undefined\") return;\n if ((instance as any).__nodeRuntimePluginLoaded) return;\n\n const mod: any = await import(\"@module-federation/node/runtimePlugin\");\n const factory = mod?.default ?? mod;\n const plugin = typeof factory === \"function\" ? factory() : null;\n if (plugin) {\n instance.registerPlugins([plugin]);\n }\n (instance as any).__nodeRuntimePluginLoaded = true;\n}\n"],"mappings":"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"}
package/dist/mf.d.cts CHANGED
@@ -1,8 +1,10 @@
1
+ import { IntegrityRegistry } from "./integrity.cjs";
1
2
  import { createInstance } from "@module-federation/enhanced/runtime";
2
3
 
3
4
  //#region src/mf.d.ts
4
5
  type FederationInstance = ReturnType<typeof createInstance>;
5
6
  declare function patchManifestFetchForSsrPublicPath(mf: FederationInstance): void;
7
+ declare function installIntegrityFetchHook(mf: FederationInstance, registry: IntegrityRegistry): void;
6
8
  declare function getFederationInstance(): FederationInstance;
7
9
  declare function registerRemote(opts: {
8
10
  name: string;
@@ -15,5 +17,5 @@ declare function loadRemoteModule<T>(specifier: string, options?: {
15
17
  }): Promise<T>;
16
18
  declare function ensureNodeRuntimePlugin(): Promise<void>;
17
19
  //#endregion
18
- export { ensureNodeRuntimePlugin, getFederationInstance, loadRemoteModule, patchManifestFetchForSsrPublicPath, registerRemote };
20
+ export { ensureNodeRuntimePlugin, getFederationInstance, installIntegrityFetchHook, loadRemoteModule, patchManifestFetchForSsrPublicPath, registerRemote };
19
21
  //# sourceMappingURL=mf.d.cts.map
package/dist/mf.d.cts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mf.d.cts","names":[],"sources":["../src/mf.ts"],"mappings":";;;KAGK,kBAAA,GAAqB,UAAA,QAAkB,cAAA;AAAA,iBAI5B,kCAAA,CAAmC,EAAA,EAAI,kBAAA;AAAA,iBAsBvC,qBAAA,CAAA,GAAyB,kBAAA;AAAA,iBAoBnB,cAAA,CAAe,IAAA;EACnC,IAAA;EACA,KAAA;EACA,IAAA;AAAA,IACE,OAAA;AAAA,iBAqBkB,gBAAA,GAAA,CACpB,SAAA,UACA,OAAA;EAAY,WAAA;EAAuB,IAAA;AAAA,IAClC,OAAA,CAAQ,CAAA;AAAA,iBAeW,uBAAA,CAAA,GAA2B,OAAA"}
1
+ {"version":3,"file":"mf.d.cts","names":[],"sources":["../src/mf.ts"],"mappings":";;;;KAIK,kBAAA,GAAqB,UAAA,QAAkB,cAAA;AAAA,iBAI5B,kCAAA,CAAmC,EAAA,EAAI,kBAAA;AAAA,iBAsBvC,yBAAA,CACd,EAAA,EAAI,kBAAA,EACJ,QAAA,EAAU,iBAAA;AAAA,iBAuCI,qBAAA,CAAA,GAAyB,kBAAA;AAAA,iBAoBnB,cAAA,CAAe,IAAA;EACnC,IAAA;EACA,KAAA;EACA,IAAA;AAAA,IACE,OAAA;AAAA,iBAqBkB,gBAAA,GAAA,CACpB,SAAA,UACA,OAAA;EAAY,WAAA;EAAuB,IAAA;AAAA,IAClC,OAAA,CAAQ,CAAA;AAAA,iBAeW,uBAAA,CAAA,GAA2B,OAAA"}
package/dist/mf.d.mts CHANGED
@@ -1,8 +1,10 @@
1
+ import { IntegrityRegistry } from "./integrity.mjs";
1
2
  import { createInstance } from "@module-federation/enhanced/runtime";
2
3
 
3
4
  //#region src/mf.d.ts
4
5
  type FederationInstance = ReturnType<typeof createInstance>;
5
6
  declare function patchManifestFetchForSsrPublicPath(mf: FederationInstance): void;
7
+ declare function installIntegrityFetchHook(mf: FederationInstance, registry: IntegrityRegistry): void;
6
8
  declare function getFederationInstance(): FederationInstance;
7
9
  declare function registerRemote(opts: {
8
10
  name: string;
@@ -15,5 +17,5 @@ declare function loadRemoteModule<T>(specifier: string, options?: {
15
17
  }): Promise<T>;
16
18
  declare function ensureNodeRuntimePlugin(): Promise<void>;
17
19
  //#endregion
18
- export { ensureNodeRuntimePlugin, getFederationInstance, loadRemoteModule, patchManifestFetchForSsrPublicPath, registerRemote };
20
+ export { ensureNodeRuntimePlugin, getFederationInstance, installIntegrityFetchHook, loadRemoteModule, patchManifestFetchForSsrPublicPath, registerRemote };
19
21
  //# sourceMappingURL=mf.d.mts.map
package/dist/mf.d.mts.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mf.d.mts","names":[],"sources":["../src/mf.ts"],"mappings":";;;KAGK,kBAAA,GAAqB,UAAA,QAAkB,cAAA;AAAA,iBAI5B,kCAAA,CAAmC,EAAA,EAAI,kBAAA;AAAA,iBAsBvC,qBAAA,CAAA,GAAyB,kBAAA;AAAA,iBAoBnB,cAAA,CAAe,IAAA;EACnC,IAAA;EACA,KAAA;EACA,IAAA;AAAA,IACE,OAAA;AAAA,iBAqBkB,gBAAA,GAAA,CACpB,SAAA,UACA,OAAA;EAAY,WAAA;EAAuB,IAAA;AAAA,IAClC,OAAA,CAAQ,CAAA;AAAA,iBAeW,uBAAA,CAAA,GAA2B,OAAA"}
1
+ {"version":3,"file":"mf.d.mts","names":[],"sources":["../src/mf.ts"],"mappings":";;;;KAIK,kBAAA,GAAqB,UAAA,QAAkB,cAAA;AAAA,iBAI5B,kCAAA,CAAmC,EAAA,EAAI,kBAAA;AAAA,iBAsBvC,yBAAA,CACd,EAAA,EAAI,kBAAA,EACJ,QAAA,EAAU,iBAAA;AAAA,iBAuCI,qBAAA,CAAA,GAAyB,kBAAA;AAAA,iBAoBnB,cAAA,CAAe,IAAA;EACnC,IAAA;EACA,KAAA;EACA,IAAA;AAAA,IACE,OAAA;AAAA,iBAqBkB,gBAAA,GAAA,CACpB,SAAA,UACA,OAAA;EAAY,WAAA;EAAuB,IAAA;AAAA,IAClC,OAAA,CAAQ,CAAA;AAAA,iBAeW,uBAAA,CAAA,GAA2B,OAAA"}
package/dist/mf.mjs CHANGED
@@ -1,3 +1,4 @@
1
+ import { computeSriHash } from "./integrity.mjs";
1
2
  import { createInstance, getInstance } from "@module-federation/enhanced/runtime";
2
3
  import { setGlobalFederationInstance } from "@module-federation/runtime-core";
3
4
 
@@ -16,6 +17,36 @@ function patchManifestFetchForSsrPublicPath(mf) {
16
17
  });
17
18
  });
18
19
  }
20
+ function installIntegrityFetchHook(mf, registry) {
21
+ if (!mf || !mf.loaderHook?.lifecycle?.fetch?.on) {
22
+ console.warn("[SRI] MF lifecycle fetch hook not available, skipping integrity-in-pipeline");
23
+ return;
24
+ }
25
+ if (mf.__everythingDevIntegrityHook === true) return;
26
+ mf.__everythingDevIntegrityHook = true;
27
+ mf.loaderHook.lifecycle.fetch.on((url, init) => {
28
+ if (typeof url !== "string") return;
29
+ const expectedHash = registry.get(url);
30
+ if (!expectedHash) return;
31
+ return fetch(url, init).then(async (res) => {
32
+ const buffer = Buffer.from(await res.arrayBuffer());
33
+ const computed = computeSriHash(buffer);
34
+ if (computed !== expectedHash) {
35
+ console.error(`[SRI] Integrity check failed in MF fetch pipeline for ${url}\n Expected: ${expectedHash}\n Computed: ${computed}`);
36
+ return new Response(`Integrity check failed for ${url}`, {
37
+ status: 500,
38
+ statusText: "Integrity Check Failed"
39
+ });
40
+ }
41
+ console.log(`[SRI] Integrity verified in pipeline for ${url}`);
42
+ return new Response(buffer, {
43
+ status: res.status,
44
+ statusText: res.statusText,
45
+ headers: res.headers
46
+ });
47
+ });
48
+ });
49
+ }
19
50
  function getFederationInstance() {
20
51
  if (mfInstance) return mfInstance;
21
52
  const existing = getInstance();
@@ -67,5 +98,5 @@ async function ensureNodeRuntimePlugin() {
67
98
  }
68
99
 
69
100
  //#endregion
70
- export { ensureNodeRuntimePlugin, getFederationInstance, loadRemoteModule, patchManifestFetchForSsrPublicPath, registerRemote };
101
+ export { ensureNodeRuntimePlugin, getFederationInstance, installIntegrityFetchHook, loadRemoteModule, patchManifestFetchForSsrPublicPath, registerRemote };
71
102
  //# sourceMappingURL=mf.mjs.map
package/dist/mf.mjs.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"mf.mjs","names":[],"sources":["../src/mf.ts"],"sourcesContent":["import { createInstance, getInstance } from \"@module-federation/enhanced/runtime\";\nimport { setGlobalFederationInstance } from \"@module-federation/runtime-core\";\n\ntype FederationInstance = ReturnType<typeof createInstance>;\n\nlet mfInstance: FederationInstance | null = null;\n\nexport function patchManifestFetchForSsrPublicPath(mf: FederationInstance): void {\n if (!mf || !(mf as any).loaderHook?.lifecycle?.fetch?.on) return;\n if ((mf as any).__everythingDevPatchedManifestFetch === true) return;\n (mf as any).__everythingDevPatchedManifestFetch = true;\n\n (mf as any).loaderHook.lifecycle.fetch.on((url: unknown, init: unknown) => {\n if (typeof url !== \"string\" || !url.endsWith(\"/mf-manifest.json\")) {\n return;\n }\n return fetch(url, init as any)\n .then((res) => res.json())\n .then((json: any) => {\n json.metaData = json.metaData ?? {};\n json.metaData.ssrPublicPath =\n json.metaData.ssrPublicPath ?? url.replace(/\\/mf-manifest\\.json$/, \"/\");\n return new Response(JSON.stringify(json), {\n headers: { \"content-type\": \"application/json\" },\n });\n });\n });\n}\n\nexport function getFederationInstance(): FederationInstance {\n if (mfInstance) return mfInstance;\n\n const existing = getInstance();\n if (existing) {\n mfInstance = existing as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n }\n\n mfInstance = createInstance({\n name: \"host\",\n remotes: [],\n }) as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n}\n\nexport async function registerRemote(opts: {\n name: string;\n entry: string;\n type?: \"manifest\" | \"script\";\n}): Promise<void> {\n const instance = getFederationInstance();\n\n const inferType = (): \"manifest\" | \"script\" => {\n if (opts.type) return opts.type;\n if (opts.entry.endsWith(\"/mf-manifest.json\")) return \"manifest\";\n if (opts.entry.endsWith(\"/remoteEntry.js\")) return \"script\";\n return typeof window === \"undefined\" ? \"script\" : \"manifest\";\n };\n\n const remoteType = inferType();\n\n instance.registerRemotes([\n {\n name: opts.name,\n entry: opts.entry,\n type: remoteType,\n },\n ]);\n}\n\nexport async function loadRemoteModule<T>(\n specifier: string,\n options?: { loadFactory?: boolean; from?: \"build\" | \"runtime\" },\n): Promise<T> {\n const instance = getFederationInstance();\n\n const isServer = typeof window === \"undefined\";\n if (isServer) {\n await (instance as any).initializeSharing?.(\"default\");\n }\n\n const mod = await instance.loadRemote<T>(specifier, options as any);\n if (!mod) {\n throw new Error(`Failed to load remote module: ${specifier}`);\n }\n return mod;\n}\n\nexport async function ensureNodeRuntimePlugin(): Promise<void> {\n const instance = getFederationInstance();\n if (typeof window !== \"undefined\") return;\n if ((instance as any).__nodeRuntimePluginLoaded) return;\n\n const mod: any = await import(\"@module-federation/node/runtimePlugin\");\n const factory = mod?.default ?? mod;\n const plugin = typeof factory === \"function\" ? factory() : null;\n if (plugin) {\n instance.registerPlugins([plugin]);\n }\n (instance as any).__nodeRuntimePluginLoaded = true;\n}\n"],"mappings":";;;;AAKA,IAAI,aAAwC;AAE5C,SAAgB,mCAAmC,IAA8B;AAC/E,KAAI,CAAC,MAAM,CAAE,GAAW,YAAY,WAAW,OAAO,GAAI;AAC1D,KAAK,GAAW,wCAAwC,KAAM;AAC9D,CAAC,GAAW,sCAAsC;AAElD,CAAC,GAAW,WAAW,UAAU,MAAM,IAAI,KAAc,SAAkB;AACzE,MAAI,OAAO,QAAQ,YAAY,CAAC,IAAI,SAAS,oBAAoB,CAC/D;AAEF,SAAO,MAAM,KAAK,KAAY,CAC3B,MAAM,QAAQ,IAAI,MAAM,CAAC,CACzB,MAAM,SAAc;AACnB,QAAK,WAAW,KAAK,YAAY,EAAE;AACnC,QAAK,SAAS,gBACZ,KAAK,SAAS,iBAAiB,IAAI,QAAQ,wBAAwB,IAAI;AACzE,UAAO,IAAI,SAAS,KAAK,UAAU,KAAK,EAAE,EACxC,SAAS,EAAE,gBAAgB,oBAAoB,EAChD,CAAC;IACF;GACJ;;AAGJ,SAAgB,wBAA4C;AAC1D,KAAI,WAAY,QAAO;CAEvB,MAAM,WAAW,aAAa;AAC9B,KAAI,UAAU;AACZ,eAAa;AACb,8BAA4B,WAAkB;AAC9C,qCAAmC,WAAW;AAC9C,SAAO;;AAGT,cAAa,eAAe;EAC1B,MAAM;EACN,SAAS,EAAE;EACZ,CAAC;AACF,6BAA4B,WAAkB;AAC9C,oCAAmC,WAAW;AAC9C,QAAO;;AAGT,eAAsB,eAAe,MAInB;CAChB,MAAM,WAAW,uBAAuB;CAExC,MAAM,kBAAyC;AAC7C,MAAI,KAAK,KAAM,QAAO,KAAK;AAC3B,MAAI,KAAK,MAAM,SAAS,oBAAoB,CAAE,QAAO;AACrD,MAAI,KAAK,MAAM,SAAS,kBAAkB,CAAE,QAAO;AACnD,SAAO,OAAO,WAAW,cAAc,WAAW;;CAGpD,MAAM,aAAa,WAAW;AAE9B,UAAS,gBAAgB,CACvB;EACE,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,MAAM;EACP,CACF,CAAC;;AAGJ,eAAsB,iBACpB,WACA,SACY;CACZ,MAAM,WAAW,uBAAuB;AAGxC,KADiB,OAAO,WAAW,YAEjC,OAAO,SAAiB,oBAAoB,UAAU;CAGxD,MAAM,MAAM,MAAM,SAAS,WAAc,WAAW,QAAe;AACnE,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,iCAAiC,YAAY;AAE/D,QAAO;;AAGT,eAAsB,0BAAyC;CAC7D,MAAM,WAAW,uBAAuB;AACxC,KAAI,OAAO,WAAW,YAAa;AACnC,KAAK,SAAiB,0BAA2B;CAEjD,MAAM,MAAW,MAAM,OAAO;CAC9B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,SAAS,OAAO,YAAY,aAAa,SAAS,GAAG;AAC3D,KAAI,OACF,UAAS,gBAAgB,CAAC,OAAO,CAAC;AAEpC,CAAC,SAAiB,4BAA4B"}
1
+ {"version":3,"file":"mf.mjs","names":[],"sources":["../src/mf.ts"],"sourcesContent":["import { createInstance, getInstance } from \"@module-federation/enhanced/runtime\";\nimport { setGlobalFederationInstance } from \"@module-federation/runtime-core\";\nimport { computeSriHash, type IntegrityRegistry } from \"./integrity\";\n\ntype FederationInstance = ReturnType<typeof createInstance>;\n\nlet mfInstance: FederationInstance | null = null;\n\nexport function patchManifestFetchForSsrPublicPath(mf: FederationInstance): void {\n if (!mf || !(mf as any).loaderHook?.lifecycle?.fetch?.on) return;\n if ((mf as any).__everythingDevPatchedManifestFetch === true) return;\n (mf as any).__everythingDevPatchedManifestFetch = true;\n\n (mf as any).loaderHook.lifecycle.fetch.on((url: unknown, init: unknown) => {\n if (typeof url !== \"string\" || !url.endsWith(\"/mf-manifest.json\")) {\n return;\n }\n return fetch(url, init as any)\n .then((res) => res.json())\n .then((json: any) => {\n json.metaData = json.metaData ?? {};\n json.metaData.ssrPublicPath =\n json.metaData.ssrPublicPath ?? url.replace(/\\/mf-manifest\\.json$/, \"/\");\n return new Response(JSON.stringify(json), {\n headers: { \"content-type\": \"application/json\" },\n });\n });\n });\n}\n\nexport function installIntegrityFetchHook(\n mf: FederationInstance,\n registry: IntegrityRegistry,\n): void {\n if (!mf || !(mf as any).loaderHook?.lifecycle?.fetch?.on) {\n console.warn(\"[SRI] MF lifecycle fetch hook not available, skipping integrity-in-pipeline\");\n return;\n }\n if ((mf as any).__everythingDevIntegrityHook === true) return;\n (mf as any).__everythingDevIntegrityHook = true;\n\n (mf as any).loaderHook.lifecycle.fetch.on((url: unknown, init: unknown) => {\n if (typeof url !== \"string\") return;\n\n const expectedHash = registry.get(url);\n if (!expectedHash) return;\n\n return fetch(url, init as any).then(async (res) => {\n const buffer = Buffer.from(await res.arrayBuffer());\n const computed = computeSriHash(buffer);\n\n if (computed !== expectedHash) {\n console.error(\n `[SRI] Integrity check failed in MF fetch pipeline for ${url}\\n Expected: ${expectedHash}\\n Computed: ${computed}`,\n );\n return new Response(`Integrity check failed for ${url}`, {\n status: 500,\n statusText: \"Integrity Check Failed\",\n });\n }\n\n console.log(`[SRI] Integrity verified in pipeline for ${url}`);\n return new Response(buffer, {\n status: res.status,\n statusText: res.statusText,\n headers: res.headers,\n });\n });\n });\n}\n\nexport function getFederationInstance(): FederationInstance {\n if (mfInstance) return mfInstance;\n\n const existing = getInstance();\n if (existing) {\n mfInstance = existing as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n }\n\n mfInstance = createInstance({\n name: \"host\",\n remotes: [],\n }) as FederationInstance;\n setGlobalFederationInstance(mfInstance as any);\n patchManifestFetchForSsrPublicPath(mfInstance);\n return mfInstance;\n}\n\nexport async function registerRemote(opts: {\n name: string;\n entry: string;\n type?: \"manifest\" | \"script\";\n}): Promise<void> {\n const instance = getFederationInstance();\n\n const inferType = (): \"manifest\" | \"script\" => {\n if (opts.type) return opts.type;\n if (opts.entry.endsWith(\"/mf-manifest.json\")) return \"manifest\";\n if (opts.entry.endsWith(\"/remoteEntry.js\")) return \"script\";\n return typeof window === \"undefined\" ? \"script\" : \"manifest\";\n };\n\n const remoteType = inferType();\n\n instance.registerRemotes([\n {\n name: opts.name,\n entry: opts.entry,\n type: remoteType,\n },\n ]);\n}\n\nexport async function loadRemoteModule<T>(\n specifier: string,\n options?: { loadFactory?: boolean; from?: \"build\" | \"runtime\" },\n): Promise<T> {\n const instance = getFederationInstance();\n\n const isServer = typeof window === \"undefined\";\n if (isServer) {\n await (instance as any).initializeSharing?.(\"default\");\n }\n\n const mod = await instance.loadRemote<T>(specifier, options as any);\n if (!mod) {\n throw new Error(`Failed to load remote module: ${specifier}`);\n }\n return mod;\n}\n\nexport async function ensureNodeRuntimePlugin(): Promise<void> {\n const instance = getFederationInstance();\n if (typeof window !== \"undefined\") return;\n if ((instance as any).__nodeRuntimePluginLoaded) return;\n\n const mod: any = await import(\"@module-federation/node/runtimePlugin\");\n const factory = mod?.default ?? mod;\n const plugin = typeof factory === \"function\" ? factory() : null;\n if (plugin) {\n instance.registerPlugins([plugin]);\n }\n (instance as any).__nodeRuntimePluginLoaded = true;\n}\n"],"mappings":";;;;;AAMA,IAAI,aAAwC;AAE5C,SAAgB,mCAAmC,IAA8B;AAC/E,KAAI,CAAC,MAAM,CAAE,GAAW,YAAY,WAAW,OAAO,GAAI;AAC1D,KAAK,GAAW,wCAAwC,KAAM;AAC9D,CAAC,GAAW,sCAAsC;AAElD,CAAC,GAAW,WAAW,UAAU,MAAM,IAAI,KAAc,SAAkB;AACzE,MAAI,OAAO,QAAQ,YAAY,CAAC,IAAI,SAAS,oBAAoB,CAC/D;AAEF,SAAO,MAAM,KAAK,KAAY,CAC3B,MAAM,QAAQ,IAAI,MAAM,CAAC,CACzB,MAAM,SAAc;AACnB,QAAK,WAAW,KAAK,YAAY,EAAE;AACnC,QAAK,SAAS,gBACZ,KAAK,SAAS,iBAAiB,IAAI,QAAQ,wBAAwB,IAAI;AACzE,UAAO,IAAI,SAAS,KAAK,UAAU,KAAK,EAAE,EACxC,SAAS,EAAE,gBAAgB,oBAAoB,EAChD,CAAC;IACF;GACJ;;AAGJ,SAAgB,0BACd,IACA,UACM;AACN,KAAI,CAAC,MAAM,CAAE,GAAW,YAAY,WAAW,OAAO,IAAI;AACxD,UAAQ,KAAK,8EAA8E;AAC3F;;AAEF,KAAK,GAAW,iCAAiC,KAAM;AACvD,CAAC,GAAW,+BAA+B;AAE3C,CAAC,GAAW,WAAW,UAAU,MAAM,IAAI,KAAc,SAAkB;AACzE,MAAI,OAAO,QAAQ,SAAU;EAE7B,MAAM,eAAe,SAAS,IAAI,IAAI;AACtC,MAAI,CAAC,aAAc;AAEnB,SAAO,MAAM,KAAK,KAAY,CAAC,KAAK,OAAO,QAAQ;GACjD,MAAM,SAAS,OAAO,KAAK,MAAM,IAAI,aAAa,CAAC;GACnD,MAAM,WAAW,eAAe,OAAO;AAEvC,OAAI,aAAa,cAAc;AAC7B,YAAQ,MACN,yDAAyD,IAAI,gBAAgB,aAAa,gBAAgB,WAC3G;AACD,WAAO,IAAI,SAAS,8BAA8B,OAAO;KACvD,QAAQ;KACR,YAAY;KACb,CAAC;;AAGJ,WAAQ,IAAI,4CAA4C,MAAM;AAC9D,UAAO,IAAI,SAAS,QAAQ;IAC1B,QAAQ,IAAI;IACZ,YAAY,IAAI;IAChB,SAAS,IAAI;IACd,CAAC;IACF;GACF;;AAGJ,SAAgB,wBAA4C;AAC1D,KAAI,WAAY,QAAO;CAEvB,MAAM,WAAW,aAAa;AAC9B,KAAI,UAAU;AACZ,eAAa;AACb,8BAA4B,WAAkB;AAC9C,qCAAmC,WAAW;AAC9C,SAAO;;AAGT,cAAa,eAAe;EAC1B,MAAM;EACN,SAAS,EAAE;EACZ,CAAC;AACF,6BAA4B,WAAkB;AAC9C,oCAAmC,WAAW;AAC9C,QAAO;;AAGT,eAAsB,eAAe,MAInB;CAChB,MAAM,WAAW,uBAAuB;CAExC,MAAM,kBAAyC;AAC7C,MAAI,KAAK,KAAM,QAAO,KAAK;AAC3B,MAAI,KAAK,MAAM,SAAS,oBAAoB,CAAE,QAAO;AACrD,MAAI,KAAK,MAAM,SAAS,kBAAkB,CAAE,QAAO;AACnD,SAAO,OAAO,WAAW,cAAc,WAAW;;CAGpD,MAAM,aAAa,WAAW;AAE9B,UAAS,gBAAgB,CACvB;EACE,MAAM,KAAK;EACX,OAAO,KAAK;EACZ,MAAM;EACP,CACF,CAAC;;AAGJ,eAAsB,iBACpB,WACA,SACY;CACZ,MAAM,WAAW,uBAAuB;AAGxC,KADiB,OAAO,WAAW,YAEjC,OAAO,SAAiB,oBAAoB,UAAU;CAGxD,MAAM,MAAM,MAAM,SAAS,WAAc,WAAW,QAAe;AACnE,KAAI,CAAC,IACH,OAAM,IAAI,MAAM,iCAAiC,YAAY;AAE/D,QAAO;;AAGT,eAAsB,0BAAyC;CAC7D,MAAM,WAAW,uBAAuB;AACxC,KAAI,OAAO,WAAW,YAAa;AACnC,KAAK,SAAiB,0BAA2B;CAEjD,MAAM,MAAW,MAAM,OAAO;CAC9B,MAAM,UAAU,KAAK,WAAW;CAChC,MAAM,SAAS,OAAO,YAAY,aAAa,SAAS,GAAG;AAC3D,KAAI,OACF,UAAS,gBAAgB,CAAC,OAAO,CAAC;AAEpC,CAAC,SAAiB,4BAA4B"}