@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.9 → 3.9.3-ultramodern.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.
Files changed (275) hide show
  1. package/README.md +23 -0
  2. package/dist/cjs/backend-federation-build/codegen.js +29 -18
  3. package/dist/cjs/backend-federation-build/config.js +84 -45
  4. package/dist/cjs/backend-federation-build/emit.js +63 -22
  5. package/dist/cjs/build-config/build-environment.js +25 -162
  6. package/dist/cjs/build-config/internal-effect-discovery.js +143 -0
  7. package/dist/cjs/build-config/public.js +2 -5
  8. package/dist/cjs/build-config/remote-address.js +58 -0
  9. package/dist/cjs/cloudflare/artifacts.js +8 -19
  10. package/dist/cjs/cloudflare/delivery-unit.js +125 -43
  11. package/dist/cjs/cloudflare/index.js +14 -4
  12. package/dist/cjs/cloudflare/native-renderer.js +100 -0
  13. package/dist/cjs/cloudflare/renderer-identity.js +106 -0
  14. package/dist/cjs/cloudflare/worker-manifest.js +54 -13
  15. package/dist/cjs/cloudflare/worker-options.js +48 -0
  16. package/dist/cjs/cloudflare/wrangler-config.js +210 -13
  17. package/dist/cjs/cloudflare-builder.js +307 -89
  18. package/dist/cjs/cloudflare-output-contract.js +36 -4
  19. package/dist/cjs/cloudflare-output-verifier/identity.js +112 -23
  20. package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
  21. package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
  22. package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
  23. package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +10 -5
  24. package/dist/cjs/config.js +10 -5
  25. package/dist/cjs/deploy-output/npmAliases.js +92 -6
  26. package/dist/cjs/deploy-output/plugin.js +27 -6
  27. package/dist/cjs/deploy-output/public-assets.js +146 -0
  28. package/dist/cjs/deploy-output/target.js +57 -1
  29. package/dist/cjs/deploy-output/trace-files.js +99 -0
  30. package/dist/cjs/module-federation-config/exposes.js +70 -0
  31. package/dist/cjs/module-federation-config/index.js +73 -0
  32. package/dist/cjs/module-federation-config/inspect.js +103 -0
  33. package/dist/cjs/module-federation-config/object-literal.js +87 -0
  34. package/dist/cjs/module-federation-config/syntax.js +151 -0
  35. package/dist/cjs/module-federation-config/types.js +18 -0
  36. package/dist/cjs/native-type-checker.js +130 -13
  37. package/dist/cjs/policy-defaults.js +38 -29
  38. package/dist/cjs/release-envelope/canonical.js +2 -9
  39. package/dist/cjs/release-envelope/contract.js +68 -0
  40. package/dist/cjs/release-envelope/framework-output.js +296 -105
  41. package/dist/cjs/release-envelope/index.js +150 -36
  42. package/dist/cjs/release-envelope/plugin.js +24 -15
  43. package/dist/cjs/release-envelope/renderer-output-stamp.js +170 -0
  44. package/dist/cjs/release-envelope/types.js +10 -8
  45. package/dist/cjs/release-identity.js +8 -5
  46. package/dist/cjs/renderer-build-identity.js +134 -0
  47. package/dist/cjs/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  48. package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  49. package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  50. package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  51. package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  52. package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
  53. package/dist/cjs/workspace-topology.js +84 -0
  54. package/dist/esm/backend-federation-build/codegen.mjs +29 -18
  55. package/dist/esm/backend-federation-build/config.mjs +74 -38
  56. package/dist/esm/backend-federation-build/emit.mjs +64 -23
  57. package/dist/esm/build-config/build-environment.mjs +26 -160
  58. package/dist/esm/build-config/internal-effect-discovery.mjs +102 -0
  59. package/dist/esm/build-config/public.mjs +1 -1
  60. package/dist/esm/build-config/remote-address.mjs +20 -0
  61. package/dist/esm/cloudflare/artifacts.mjs +8 -19
  62. package/dist/esm/cloudflare/delivery-unit.mjs +124 -45
  63. package/dist/esm/cloudflare/index.mjs +15 -5
  64. package/dist/esm/cloudflare/native-renderer.mjs +48 -0
  65. package/dist/esm/cloudflare/renderer-identity.mjs +57 -0
  66. package/dist/esm/cloudflare/worker-manifest.mjs +55 -14
  67. package/dist/esm/cloudflare/worker-options.mjs +10 -0
  68. package/dist/esm/cloudflare/wrangler-config.mjs +203 -12
  69. package/dist/esm/cloudflare-builder.mjs +301 -89
  70. package/dist/esm/cloudflare-output-contract.mjs +29 -3
  71. package/dist/esm/cloudflare-output-verifier/identity.mjs +113 -24
  72. package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
  73. package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
  74. package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  75. package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  76. package/dist/esm/config.mjs +3 -1
  77. package/dist/esm/deploy-output/npmAliases.mjs +92 -6
  78. package/dist/esm/deploy-output/plugin.mjs +24 -6
  79. package/dist/esm/deploy-output/public-assets.mjs +82 -0
  80. package/dist/esm/deploy-output/target.mjs +50 -3
  81. package/dist/esm/deploy-output/trace-files.mjs +47 -0
  82. package/dist/esm/module-federation-config/exposes.mjs +18 -0
  83. package/dist/esm/module-federation-config/index.mjs +4 -0
  84. package/dist/esm/module-federation-config/inspect.mjs +65 -0
  85. package/dist/esm/module-federation-config/object-literal.mjs +43 -0
  86. package/dist/esm/module-federation-config/syntax.mjs +101 -0
  87. package/dist/esm/module-federation-config/types.mjs +0 -0
  88. package/dist/esm/native-type-checker.mjs +124 -13
  89. package/dist/esm/policy-defaults.mjs +36 -30
  90. package/dist/esm/release-envelope/canonical.mjs +2 -9
  91. package/dist/esm/release-envelope/contract.mjs +21 -0
  92. package/dist/esm/release-envelope/framework-output.mjs +297 -106
  93. package/dist/esm/release-envelope/index.mjs +149 -38
  94. package/dist/esm/release-envelope/plugin.mjs +24 -15
  95. package/dist/esm/release-envelope/renderer-output-stamp.mjs +112 -0
  96. package/dist/esm/release-envelope/types.mjs +2 -3
  97. package/dist/esm/release-identity.mjs +8 -5
  98. package/dist/esm/renderer-build-identity.mjs +82 -0
  99. package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  100. package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  101. package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  102. package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  103. package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  104. package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
  105. package/dist/esm/workspace-topology.mjs +21 -0
  106. package/dist/esm-node/backend-federation-build/codegen.mjs +29 -18
  107. package/dist/esm-node/backend-federation-build/config.mjs +74 -38
  108. package/dist/esm-node/backend-federation-build/emit.mjs +64 -23
  109. package/dist/esm-node/build-config/build-environment.mjs +26 -160
  110. package/dist/esm-node/build-config/internal-effect-discovery.mjs +103 -0
  111. package/dist/esm-node/build-config/public.mjs +1 -1
  112. package/dist/esm-node/build-config/remote-address.mjs +21 -0
  113. package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
  114. package/dist/esm-node/cloudflare/delivery-unit.mjs +124 -45
  115. package/dist/esm-node/cloudflare/index.mjs +15 -5
  116. package/dist/esm-node/cloudflare/native-renderer.mjs +49 -0
  117. package/dist/esm-node/cloudflare/renderer-identity.mjs +58 -0
  118. package/dist/esm-node/cloudflare/worker-manifest.mjs +55 -14
  119. package/dist/esm-node/cloudflare/worker-options.mjs +11 -0
  120. package/dist/esm-node/cloudflare/wrangler-config.mjs +203 -12
  121. package/dist/esm-node/cloudflare-builder.mjs +301 -89
  122. package/dist/esm-node/cloudflare-output-contract.mjs +29 -3
  123. package/dist/esm-node/cloudflare-output-verifier/identity.mjs +113 -24
  124. package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
  125. package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
  126. package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  127. package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  128. package/dist/esm-node/config.mjs +3 -1
  129. package/dist/esm-node/deploy-output/npmAliases.mjs +92 -6
  130. package/dist/esm-node/deploy-output/plugin.mjs +24 -6
  131. package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
  132. package/dist/esm-node/deploy-output/target.mjs +50 -3
  133. package/dist/esm-node/deploy-output/trace-files.mjs +48 -0
  134. package/dist/esm-node/module-federation-config/exposes.mjs +19 -0
  135. package/dist/esm-node/module-federation-config/index.mjs +5 -0
  136. package/dist/esm-node/module-federation-config/inspect.mjs +66 -0
  137. package/dist/esm-node/module-federation-config/object-literal.mjs +44 -0
  138. package/dist/esm-node/module-federation-config/syntax.mjs +102 -0
  139. package/dist/esm-node/module-federation-config/types.mjs +1 -0
  140. package/dist/esm-node/native-type-checker.mjs +124 -13
  141. package/dist/esm-node/policy-defaults.mjs +36 -30
  142. package/dist/esm-node/release-envelope/canonical.mjs +2 -9
  143. package/dist/esm-node/release-envelope/contract.mjs +22 -0
  144. package/dist/esm-node/release-envelope/framework-output.mjs +297 -106
  145. package/dist/esm-node/release-envelope/index.mjs +149 -38
  146. package/dist/esm-node/release-envelope/plugin.mjs +24 -15
  147. package/dist/esm-node/release-envelope/renderer-output-stamp.mjs +113 -0
  148. package/dist/esm-node/release-envelope/types.mjs +2 -3
  149. package/dist/esm-node/release-identity.mjs +8 -5
  150. package/dist/esm-node/renderer-build-identity.mjs +83 -0
  151. package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  152. package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  153. package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  154. package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  155. package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  156. package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
  157. package/dist/esm-node/workspace-topology.mjs +22 -0
  158. package/dist/types/backend-federation-build/codegen.d.ts +30 -0
  159. package/dist/types/backend-federation-build/config.d.ts +10 -57
  160. package/dist/types/backend-federation-build/emit.d.ts +12 -2
  161. package/dist/types/backend-federation-build/index.d.ts +1 -1
  162. package/dist/types/build-config/build-environment.d.ts +1 -9
  163. package/dist/types/build-config/internal-effect-discovery.d.ts +4 -0
  164. package/dist/types/build-config/public.d.ts +1 -1
  165. package/dist/types/build-config/remote-address.d.ts +3 -0
  166. package/dist/types/cloudflare/artifacts.d.ts +7 -6
  167. package/dist/types/cloudflare/constants.d.ts +1 -1
  168. package/dist/types/cloudflare/delivery-unit.d.ts +38 -7
  169. package/dist/types/cloudflare/native-renderer.d.ts +30 -0
  170. package/dist/types/cloudflare/renderer-identity.d.ts +25 -0
  171. package/dist/types/cloudflare/worker-manifest.d.ts +17 -2
  172. package/dist/types/cloudflare/worker-options.d.ts +20 -0
  173. package/dist/types/cloudflare/wrangler-config.d.ts +55 -2
  174. package/dist/types/cloudflare-builder.d.ts +51 -8
  175. package/dist/types/cloudflare-output-contract.d.ts +5 -3
  176. package/dist/types/cloudflare-output-verifier/identity.d.ts +7 -3
  177. package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
  178. package/dist/types/config.d.ts +98 -13
  179. package/dist/types/deploy-output/plugin.d.ts +29 -9
  180. package/dist/types/deploy-output/public-assets.d.ts +51 -0
  181. package/dist/types/deploy-output/target.d.ts +28 -5
  182. package/dist/types/deploy-output/trace-files.d.ts +17 -0
  183. package/dist/types/module-federation-config/exposes.d.ts +11 -0
  184. package/dist/types/module-federation-config/index.d.ts +5 -0
  185. package/dist/types/module-federation-config/inspect.d.ts +2 -0
  186. package/dist/types/module-federation-config/object-literal.d.ts +4 -0
  187. package/dist/types/module-federation-config/syntax.d.ts +7 -0
  188. package/dist/types/module-federation-config/types.d.ts +20 -0
  189. package/dist/types/native-type-checker.d.ts +26 -3
  190. package/dist/types/policy-defaults.d.ts +12 -2
  191. package/dist/types/release-envelope/canonical.d.ts +5 -5
  192. package/dist/types/release-envelope/contract.d.mts +8 -0
  193. package/dist/types/release-envelope/framework-output.d.ts +23 -13
  194. package/dist/types/release-envelope/index.d.ts +17 -5
  195. package/dist/types/release-envelope/plugin.d.ts +10 -6
  196. package/dist/types/release-envelope/renderer-output-stamp.d.ts +34 -0
  197. package/dist/types/release-envelope/types.d.ts +42 -4
  198. package/dist/types/release-identity.d.ts +3 -2
  199. package/dist/types/renderer-build-identity.d.ts +30 -0
  200. package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
  201. package/dist/types/workspace-topology.d.ts +91 -0
  202. package/package.json +87 -27
  203. package/src/backend-federation-build/codegen.ts +28 -19
  204. package/src/backend-federation-build/config.ts +189 -123
  205. package/src/backend-federation-build/emit.ts +119 -38
  206. package/src/backend-federation-build/index.ts +4 -1
  207. package/src/build-config/build-environment.ts +59 -285
  208. package/src/build-config/internal-effect-discovery.ts +239 -0
  209. package/src/build-config/public.ts +0 -1
  210. package/src/build-config/remote-address.ts +33 -0
  211. package/src/cloudflare/artifacts.ts +22 -43
  212. package/src/cloudflare/delivery-unit.ts +283 -60
  213. package/src/cloudflare/index.ts +35 -19
  214. package/src/cloudflare/native-renderer.ts +117 -0
  215. package/src/cloudflare/renderer-identity.ts +171 -0
  216. package/src/cloudflare/worker-manifest.ts +108 -10
  217. package/src/cloudflare/worker-options.ts +60 -0
  218. package/src/cloudflare/wrangler-config.ts +390 -14
  219. package/src/cloudflare-builder.ts +543 -170
  220. package/src/cloudflare-output-contract.ts +45 -2
  221. package/src/cloudflare-output-verifier/identity.ts +138 -7
  222. package/src/cloudflare-output-verifier/index.ts +20 -0
  223. package/src/cloudflare-output-verifier/manifest.ts +77 -0
  224. package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
  225. package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
  226. package/src/config.ts +101 -19
  227. package/src/deploy-output/npmAliases.ts +141 -4
  228. package/src/deploy-output/plugin.ts +55 -18
  229. package/src/deploy-output/public-assets.ts +224 -0
  230. package/src/deploy-output/target.ts +97 -14
  231. package/src/deploy-output/trace-files.ts +80 -0
  232. package/src/module-federation-config/exposes.ts +46 -0
  233. package/src/module-federation-config/index.ts +22 -0
  234. package/src/module-federation-config/inspect.ts +157 -0
  235. package/src/module-federation-config/object-literal.ts +76 -0
  236. package/src/module-federation-config/syntax.ts +212 -0
  237. package/src/module-federation-config/types.ts +22 -0
  238. package/src/native-type-checker.ts +233 -20
  239. package/src/policy-defaults.ts +47 -17
  240. package/src/release-envelope/canonical.ts +6 -18
  241. package/src/release-envelope/contract.mjs +28 -0
  242. package/src/release-envelope/framework-output.ts +681 -156
  243. package/src/release-envelope/index.ts +315 -60
  244. package/src/release-envelope/plugin.ts +54 -38
  245. package/src/release-envelope/renderer-output-stamp.ts +210 -0
  246. package/src/release-envelope/types.ts +75 -6
  247. package/src/release-identity.ts +19 -2
  248. package/src/renderer-build-identity.ts +168 -0
  249. package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  250. package/src/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  251. package/src/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  252. package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  253. package/src/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  254. package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
  255. package/src/workspace-topology.ts +125 -0
  256. package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +0 -11
  257. package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +0 -281
  258. package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  259. package/dist/cjs/templates/cloudflare-worker-path.mjs +0 -98
  260. package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +0 -11
  261. package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +0 -281
  262. package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  263. package/dist/esm/templates/cloudflare-worker-path.mjs +0 -98
  264. package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +0 -11
  265. package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +0 -281
  266. package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  267. package/dist/esm-node/templates/cloudflare-worker-path.mjs +0 -98
  268. package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +0 -5
  269. package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +0 -48
  270. package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +0 -13
  271. package/dist/types/templates/cloudflare-worker-path.d.mts +0 -21
  272. package/src/templates/cloudflare-worker-fs-promises.mjs +0 -11
  273. package/src/templates/cloudflare-worker-loadable-server.mjs +0 -281
  274. package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  275. package/src/templates/cloudflare-worker-path.mjs +0 -98
@@ -0,0 +1,46 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { inspectModuleFederationConfigSource } from './inspect';
4
+ import type { ModuleFederationConfigInspection } from './types';
5
+
6
+ const moduleFederationConfigFile = 'module-federation.config.ts';
7
+
8
+ /**
9
+ * Returns undefined when the app ships no Module Federation config, or when the
10
+ * config cannot be inspected statically, so callers keep their own expectation
11
+ * rather than inventing a surface path. `ultramodern mf-types` reports those
12
+ * configs separately, so staying quiet here never hides a broken config.
13
+ * Native deployment policy loads dynamic configs through Modern.js's loader.
14
+ */
15
+ export function readModuleFederationConfigInspection(
16
+ workspaceRoot: string,
17
+ appDirectory: string,
18
+ ): ModuleFederationConfigInspection | undefined {
19
+ const configPath = path.join(
20
+ workspaceRoot,
21
+ appDirectory,
22
+ moduleFederationConfigFile,
23
+ );
24
+ if (!fs.existsSync(configPath)) {
25
+ return undefined;
26
+ }
27
+ try {
28
+ const source = fs.readFileSync(configPath, 'utf-8');
29
+ return inspectModuleFederationConfigSource(
30
+ source,
31
+ appDirectory,
32
+ path.join(appDirectory, moduleFederationConfigFile),
33
+ );
34
+ } catch {
35
+ return undefined;
36
+ }
37
+ }
38
+
39
+ /** The literal source paths, independently of the complete expose-name list. */
40
+ export function readModuleFederationExposePaths(
41
+ workspaceRoot: string,
42
+ appDirectory: string,
43
+ ): Record<string, string> | undefined {
44
+ return readModuleFederationConfigInspection(workspaceRoot, appDirectory)
45
+ ?.exposePaths;
46
+ }
@@ -0,0 +1,22 @@
1
+ export {
2
+ readModuleFederationConfigInspection,
3
+ readModuleFederationExposePaths,
4
+ } from './exposes';
5
+ export { inspectModuleFederationConfigSource } from './inspect';
6
+ export {
7
+ parseArrayLiteral,
8
+ parseLiteralString,
9
+ parseObjectLiteral,
10
+ } from './object-literal';
11
+ export {
12
+ findCreateModuleFederationConfigObject,
13
+ findExportDefaultObject,
14
+ locateCreateModuleFederationConfigObject,
15
+ parseConfigModule,
16
+ parseStaticExpression,
17
+ } from './syntax';
18
+ export type {
19
+ LocatedObjectLiteral,
20
+ ModuleFederationConfigInspection,
21
+ ParsedObjectLiteral,
22
+ } from './types';
@@ -0,0 +1,157 @@
1
+ import {
2
+ parseArrayLiteral,
3
+ parseLiteralString,
4
+ parseObjectLiteral,
5
+ } from './object-literal';
6
+ import {
7
+ findCreateModuleFederationConfigObject,
8
+ findExportDefaultObject,
9
+ parseConfigModule,
10
+ } from './syntax';
11
+ import type { ModuleFederationConfigInspection } from './types';
12
+
13
+ type ExtractedExposes = Pick<
14
+ ModuleFederationConfigInspection,
15
+ 'exposePaths' | 'exposes'
16
+ >;
17
+
18
+ function extractExposes(
19
+ configPath: string,
20
+ value: string | undefined,
21
+ ): ExtractedExposes {
22
+ if (value === undefined) {
23
+ return { exposePaths: {}, exposes: [] };
24
+ }
25
+
26
+ const object = parseObjectLiteral(value);
27
+ if (object) {
28
+ if (object.hasSpread) {
29
+ throw new Error(
30
+ `Cannot statically extract Module Federation exposes from ${configPath}; use a literal exposes object without spreads.`,
31
+ );
32
+ }
33
+ const exposePaths: Record<string, string> = {};
34
+ for (const [expose, source] of object.properties) {
35
+ const exposePath = parseLiteralString(source);
36
+ if (exposePath !== undefined) {
37
+ exposePaths[expose] = exposePath;
38
+ }
39
+ }
40
+ return {
41
+ exposePaths,
42
+ exposes: Array.from(object.properties.keys()).sort(),
43
+ };
44
+ }
45
+
46
+ const array = parseArrayLiteral(value);
47
+ if (array) {
48
+ return { exposePaths: {}, exposes: array.sort() };
49
+ }
50
+
51
+ throw new Error(
52
+ `Cannot statically extract Module Federation exposes from ${configPath}; use a literal exposes object or string array.`,
53
+ );
54
+ }
55
+
56
+ function extractDtsSettings(
57
+ configPath: string,
58
+ value: string | undefined,
59
+ exposes: string[],
60
+ ): ModuleFederationConfigInspection['dts'] {
61
+ if (value === 'false') {
62
+ if (exposes.length > 0) {
63
+ throw new Error(
64
+ `Module Federation DTS cannot be disabled for exposed app ${configPath}.`,
65
+ );
66
+ }
67
+ return {};
68
+ }
69
+ if (value === undefined) {
70
+ return {};
71
+ }
72
+
73
+ const dts = parseObjectLiteral(value);
74
+ if (!dts || dts.hasSpread) {
75
+ throw new Error(
76
+ `Cannot statically extract Module Federation DTS settings from ${configPath}; use a literal dts object.`,
77
+ );
78
+ }
79
+
80
+ const generateTypes = parseObjectLiteral(dts.properties.get('generateTypes'));
81
+ if (generateTypes?.hasSpread) {
82
+ throw new Error(
83
+ `Cannot statically extract Module Federation generateTypes settings from ${configPath}; use a literal generateTypes object.`,
84
+ );
85
+ }
86
+
87
+ const compilerInstanceSource =
88
+ generateTypes?.properties.get('compilerInstance');
89
+ return {
90
+ compilerInstance:
91
+ parseLiteralString(compilerInstanceSource) ??
92
+ (compilerInstanceSource === 'tsgoCompilerInstance'
93
+ ? 'effect-tsgo'
94
+ : undefined),
95
+ tsConfigPath: parseLiteralString(dts.properties.get('tsConfigPath')),
96
+ };
97
+ }
98
+
99
+ function hasHostOnlyNoExposesDeclaration(source: string): boolean {
100
+ return (parseConfigModule(source).comments ?? []).some(comment =>
101
+ /@?ultramodern-mf\s*:?\s*(?:host-only|no-exposes)\b/iu.test(comment.value),
102
+ );
103
+ }
104
+
105
+ export function inspectModuleFederationConfigSource(
106
+ source: string,
107
+ appDir: string,
108
+ configPath: string,
109
+ ): ModuleFederationConfigInspection {
110
+ const configObject =
111
+ findCreateModuleFederationConfigObject(source) ??
112
+ findExportDefaultObject(source);
113
+
114
+ if (!configObject) {
115
+ throw new Error(
116
+ `Cannot statically inspect Module Federation config ${configPath}; export or pass a literal config object.`,
117
+ );
118
+ }
119
+
120
+ const properties = parseObjectLiteral(configObject);
121
+ if (!properties) {
122
+ throw new Error(
123
+ `Cannot statically inspect Module Federation config ${configPath}; expected a literal config object.`,
124
+ );
125
+ }
126
+
127
+ if (properties.hasSpread) {
128
+ throw new Error(
129
+ `Cannot statically inspect Module Federation config ${configPath}; top-level config spreads are not supported.`,
130
+ );
131
+ }
132
+
133
+ const { exposePaths, exposes } = extractExposes(
134
+ configPath,
135
+ properties.properties.get('exposes'),
136
+ );
137
+ const hostOnlyNoExposes = hasHostOnlyNoExposesDeclaration(source);
138
+
139
+ if (hostOnlyNoExposes && exposes.length > 0) {
140
+ throw new Error(
141
+ `Module Federation host-only/no-exposes declaration conflicts with actual exposes in ${configPath}.`,
142
+ );
143
+ }
144
+
145
+ return {
146
+ appDir,
147
+ configPath,
148
+ dts: extractDtsSettings(
149
+ configPath,
150
+ properties.properties.get('dts'),
151
+ exposes,
152
+ ),
153
+ exposePaths,
154
+ exposes,
155
+ hostOnlyNoExposes,
156
+ };
157
+ }
@@ -0,0 +1,76 @@
1
+ import { parseStaticExpression } from './syntax';
2
+ import type { ParsedObjectLiteral } from './types';
3
+
4
+ export function parseLiteralString(
5
+ source: string | undefined,
6
+ ): string | undefined {
7
+ const node = parseStaticExpression(source);
8
+ if (node?.type === 'StringLiteral') return node.value;
9
+ if (node?.type === 'TemplateLiteral' && node.expressions.length === 0) {
10
+ return node.quasis[0]?.value.cooked ?? undefined;
11
+ }
12
+ return undefined;
13
+ }
14
+
15
+ export function parseObjectLiteral(
16
+ source: string | undefined,
17
+ ): ParsedObjectLiteral | undefined {
18
+ const node = parseStaticExpression(source);
19
+ if (source === undefined || node?.type !== 'ObjectExpression')
20
+ return undefined;
21
+ const properties = new Map<string, string>();
22
+ let hasSpread = false;
23
+ for (const property of node.properties) {
24
+ if (
25
+ property.type !== 'ObjectProperty' ||
26
+ property.computed ||
27
+ property.shorthand
28
+ ) {
29
+ hasSpread = true;
30
+ continue;
31
+ }
32
+ const key =
33
+ property.key.type === 'Identifier'
34
+ ? property.key.name
35
+ : property.key.type === 'StringLiteral'
36
+ ? property.key.value
37
+ : undefined;
38
+ const value = parseStaticExpression(
39
+ source.slice(property.value.start ?? 0, property.value.end ?? 0),
40
+ );
41
+ if (
42
+ key === undefined ||
43
+ properties.has(key) ||
44
+ !value ||
45
+ value.start == null ||
46
+ value.end == null
47
+ ) {
48
+ hasSpread = true;
49
+ continue;
50
+ }
51
+ const rawValue = source.slice(
52
+ property.value.start ?? 0,
53
+ property.value.end ?? 0,
54
+ );
55
+ properties.set(key, rawValue.slice(value.start, value.end));
56
+ }
57
+ return { hasSpread, properties };
58
+ }
59
+
60
+ export function parseArrayLiteral(
61
+ source: string | undefined,
62
+ ): string[] | undefined {
63
+ const node = parseStaticExpression(source);
64
+ if (source === undefined || node?.type !== 'ArrayExpression')
65
+ return undefined;
66
+ const values: string[] = [];
67
+ for (const element of node.elements) {
68
+ if (!element || element.type === 'SpreadElement') return undefined;
69
+ const value = parseLiteralString(
70
+ source.slice(element.start ?? 0, element.end ?? 0),
71
+ );
72
+ if (value === undefined) return undefined;
73
+ values.push(value);
74
+ }
75
+ return values;
76
+ }
@@ -0,0 +1,212 @@
1
+ import {
2
+ type Expression,
3
+ type File,
4
+ parse,
5
+ parseExpression,
6
+ } from '@babel/parser';
7
+ import type { LocatedObjectLiteral } from './types';
8
+
9
+ type ConfigNode = Expression | File['program']['body'][number];
10
+
11
+ function unwrapExpression(node: Expression): Expression {
12
+ while (
13
+ node.type === 'TSAsExpression' ||
14
+ node.type === 'TSSatisfiesExpression' ||
15
+ node.type === 'TSNonNullExpression' ||
16
+ node.type === 'TSTypeAssertion' ||
17
+ node.type === 'ParenthesizedExpression'
18
+ ) {
19
+ node = node.expression;
20
+ }
21
+ return node;
22
+ }
23
+
24
+ export function parseStaticExpression(
25
+ source: string | undefined,
26
+ ): Expression | undefined {
27
+ if (source === undefined) {
28
+ return undefined;
29
+ }
30
+ try {
31
+ return unwrapExpression(
32
+ parseExpression(source, { plugins: ['typescript'] }),
33
+ );
34
+ } catch {
35
+ return undefined;
36
+ }
37
+ }
38
+
39
+ export function parseConfigModule(source: string) {
40
+ return parse(source, { sourceType: 'module', plugins: ['typescript'] });
41
+ }
42
+
43
+ function locateExportedConfig(
44
+ source: string,
45
+ factory: boolean,
46
+ ): LocatedObjectLiteral | undefined {
47
+ const module = parseConfigModule(source);
48
+ const constants = new Map<string, Expression>();
49
+ const bindingStarts = new Map<string, number | null | undefined>();
50
+ const resolvedBindings = new Set<string>();
51
+ const allowedReferences = new Set<number>();
52
+ const factories = new Set<string>();
53
+ const namespaces = new Set<string>();
54
+ const defaults: ConfigNode[] = [];
55
+
56
+ for (const statement of module.program.body) {
57
+ if (
58
+ statement.type === 'ImportDeclaration' &&
59
+ statement.importKind !== 'type' &&
60
+ statement.source.value === '@module-federation/modern-js-v3'
61
+ ) {
62
+ for (const specifier of statement.specifiers) {
63
+ if (specifier.type === 'ImportNamespaceSpecifier') {
64
+ namespaces.add(specifier.local.name);
65
+ } else if (
66
+ specifier.type === 'ImportSpecifier' &&
67
+ specifier.importKind !== 'type' &&
68
+ (specifier.imported.type === 'Identifier'
69
+ ? specifier.imported.name
70
+ : specifier.imported.value) === 'createModuleFederationConfig'
71
+ ) {
72
+ factories.add(specifier.local.name);
73
+ }
74
+ }
75
+ }
76
+ const declaration =
77
+ statement.type === 'ExportNamedDeclaration'
78
+ ? statement.declaration
79
+ : statement;
80
+ if (
81
+ declaration?.type === 'VariableDeclaration' &&
82
+ declaration.kind === 'const'
83
+ ) {
84
+ for (const binding of declaration.declarations) {
85
+ if (binding.id.type === 'Identifier' && binding.init) {
86
+ constants.set(binding.id.name, binding.init);
87
+ bindingStarts.set(binding.id.name, binding.id.start);
88
+ }
89
+ }
90
+ }
91
+ if (statement.type === 'ExportDefaultDeclaration') {
92
+ defaults.push(statement.declaration);
93
+ } else if (
94
+ statement.type === 'ExportNamedDeclaration' &&
95
+ !statement.source &&
96
+ statement.exportKind !== 'type'
97
+ ) {
98
+ for (const specifier of statement.specifiers) {
99
+ if (
100
+ specifier.type === 'ExportSpecifier' &&
101
+ specifier.exportKind !== 'type' &&
102
+ (specifier.exported.type === 'Identifier'
103
+ ? specifier.exported.name
104
+ : specifier.exported.value) === 'default'
105
+ ) {
106
+ defaults.push(specifier.local);
107
+ }
108
+ }
109
+ }
110
+ }
111
+
112
+ const resolve = (
113
+ node: ConfigNode | undefined,
114
+ seen = new Set<string>(),
115
+ ): ConfigNode | undefined => {
116
+ if (!node) return undefined;
117
+ if (node.type === 'Identifier') {
118
+ if (seen.has(node.name)) return undefined;
119
+ seen.add(node.name);
120
+ resolvedBindings.add(node.name);
121
+ if (node.start != null) allowedReferences.add(node.start);
122
+ const bindingStart = bindingStarts.get(node.name);
123
+ if (bindingStart != null) allowedReferences.add(bindingStart);
124
+ return resolve(constants.get(node.name), seen);
125
+ }
126
+ if (
127
+ node.type === 'TSAsExpression' ||
128
+ node.type === 'TSSatisfiesExpression' ||
129
+ node.type === 'TSNonNullExpression' ||
130
+ node.type === 'TSTypeAssertion' ||
131
+ node.type === 'ParenthesizedExpression'
132
+ ) {
133
+ return resolve(node.expression, seen);
134
+ }
135
+ return node;
136
+ };
137
+
138
+ if (defaults.length !== 1) return undefined;
139
+ let node = resolve(defaults[0]);
140
+ if (factory) {
141
+ if (node?.type !== 'CallExpression' || node.arguments.length !== 1)
142
+ return undefined;
143
+ const callee = node.callee;
144
+ const supported =
145
+ (callee.type === 'Identifier' && factories.has(callee.name)) ||
146
+ (callee.type === 'MemberExpression' &&
147
+ !callee.computed &&
148
+ callee.object.type === 'Identifier' &&
149
+ namespaces.has(callee.object.name) &&
150
+ callee.property.type === 'Identifier' &&
151
+ callee.property.name === 'createModuleFederationConfig');
152
+ if (!supported) return undefined;
153
+ const argument = node.arguments[0];
154
+ if (
155
+ !argument ||
156
+ argument.type === 'SpreadElement' ||
157
+ argument.type === 'ArgumentPlaceholder'
158
+ )
159
+ return undefined;
160
+ node = resolve(argument);
161
+ }
162
+ if (
163
+ node?.type !== 'ObjectExpression' ||
164
+ node.start == null ||
165
+ node.end == null
166
+ )
167
+ return undefined;
168
+ // A const object can still be mutated or passed to arbitrary code. Only
169
+ // references followed along the exported config chain are statically safe.
170
+ const hasOtherReference = (value: unknown): boolean => {
171
+ if (!value || typeof value !== 'object') return false;
172
+ if (Array.isArray(value)) return value.some(hasOtherReference);
173
+ const entry = value as Record<string, unknown>;
174
+ if (
175
+ entry.type === 'Identifier' &&
176
+ typeof entry.name === 'string' &&
177
+ resolvedBindings.has(entry.name) &&
178
+ (typeof entry.start !== 'number' || !allowedReferences.has(entry.start))
179
+ )
180
+ return true;
181
+ return Object.entries(entry).some(
182
+ ([key, child]) =>
183
+ key !== 'loc' &&
184
+ key !== 'comments' &&
185
+ key !== 'leadingComments' &&
186
+ key !== 'trailingComments' &&
187
+ hasOtherReference(child),
188
+ );
189
+ };
190
+ if (hasOtherReference(module.program)) return undefined;
191
+ return {
192
+ start: node.start,
193
+ end: node.end,
194
+ source: source.slice(node.start, node.end),
195
+ };
196
+ }
197
+
198
+ export function locateCreateModuleFederationConfigObject(
199
+ source: string,
200
+ ): LocatedObjectLiteral | undefined {
201
+ return locateExportedConfig(source, true);
202
+ }
203
+
204
+ export function findCreateModuleFederationConfigObject(
205
+ source: string,
206
+ ): string | undefined {
207
+ return locateCreateModuleFederationConfigObject(source)?.source;
208
+ }
209
+
210
+ export function findExportDefaultObject(source: string): string | undefined {
211
+ return locateExportedConfig(source, false)?.source;
212
+ }
@@ -0,0 +1,22 @@
1
+ export type ModuleFederationConfigInspection = {
2
+ appDir: string;
3
+ configPath: string;
4
+ dts: {
5
+ compilerInstance?: string;
6
+ tsConfigPath?: string;
7
+ };
8
+ exposePaths: Record<string, string>;
9
+ exposes: string[];
10
+ hostOnlyNoExposes: boolean;
11
+ };
12
+
13
+ export type LocatedObjectLiteral = {
14
+ end: number;
15
+ source: string;
16
+ start: number;
17
+ };
18
+
19
+ export type ParsedObjectLiteral = {
20
+ hasSpread: boolean;
21
+ properties: Map<string, string>;
22
+ };