@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.8 → 3.9.3-ultramodern.1

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 (280) 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 +164 -18
  37. package/dist/cjs/policy-defaults.js +110 -0
  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/runtime-package-resolution.js +118 -0
  48. package/dist/cjs/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  49. package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  50. package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  51. package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  52. package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  53. package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
  54. package/dist/cjs/workspace-topology.js +84 -0
  55. package/dist/esm/backend-federation-build/codegen.mjs +29 -18
  56. package/dist/esm/backend-federation-build/config.mjs +74 -38
  57. package/dist/esm/backend-federation-build/emit.mjs +64 -23
  58. package/dist/esm/build-config/build-environment.mjs +26 -160
  59. package/dist/esm/build-config/internal-effect-discovery.mjs +102 -0
  60. package/dist/esm/build-config/public.mjs +1 -1
  61. package/dist/esm/build-config/remote-address.mjs +20 -0
  62. package/dist/esm/cloudflare/artifacts.mjs +8 -19
  63. package/dist/esm/cloudflare/delivery-unit.mjs +124 -45
  64. package/dist/esm/cloudflare/index.mjs +15 -5
  65. package/dist/esm/cloudflare/native-renderer.mjs +48 -0
  66. package/dist/esm/cloudflare/renderer-identity.mjs +57 -0
  67. package/dist/esm/cloudflare/worker-manifest.mjs +55 -14
  68. package/dist/esm/cloudflare/worker-options.mjs +10 -0
  69. package/dist/esm/cloudflare/wrangler-config.mjs +203 -12
  70. package/dist/esm/cloudflare-builder.mjs +301 -89
  71. package/dist/esm/cloudflare-output-contract.mjs +29 -3
  72. package/dist/esm/cloudflare-output-verifier/identity.mjs +113 -24
  73. package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
  74. package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
  75. package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  76. package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  77. package/dist/esm/config.mjs +3 -1
  78. package/dist/esm/deploy-output/npmAliases.mjs +92 -6
  79. package/dist/esm/deploy-output/plugin.mjs +24 -6
  80. package/dist/esm/deploy-output/public-assets.mjs +82 -0
  81. package/dist/esm/deploy-output/target.mjs +50 -3
  82. package/dist/esm/deploy-output/trace-files.mjs +47 -0
  83. package/dist/esm/module-federation-config/exposes.mjs +18 -0
  84. package/dist/esm/module-federation-config/index.mjs +4 -0
  85. package/dist/esm/module-federation-config/inspect.mjs +65 -0
  86. package/dist/esm/module-federation-config/object-literal.mjs +43 -0
  87. package/dist/esm/module-federation-config/syntax.mjs +101 -0
  88. package/dist/esm/module-federation-config/types.mjs +0 -0
  89. package/dist/esm/native-type-checker.mjs +158 -18
  90. package/dist/esm/policy-defaults.mjs +51 -0
  91. package/dist/esm/release-envelope/canonical.mjs +2 -9
  92. package/dist/esm/release-envelope/contract.mjs +21 -0
  93. package/dist/esm/release-envelope/framework-output.mjs +297 -106
  94. package/dist/esm/release-envelope/index.mjs +149 -38
  95. package/dist/esm/release-envelope/plugin.mjs +24 -15
  96. package/dist/esm/release-envelope/renderer-output-stamp.mjs +112 -0
  97. package/dist/esm/release-envelope/types.mjs +2 -3
  98. package/dist/esm/release-identity.mjs +8 -5
  99. package/dist/esm/renderer-build-identity.mjs +82 -0
  100. package/dist/esm/runtime-package-resolution.mjs +60 -0
  101. package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  102. package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  103. package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  104. package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  105. package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  106. package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
  107. package/dist/esm/workspace-topology.mjs +21 -0
  108. package/dist/esm-node/backend-federation-build/codegen.mjs +29 -18
  109. package/dist/esm-node/backend-federation-build/config.mjs +74 -38
  110. package/dist/esm-node/backend-federation-build/emit.mjs +64 -23
  111. package/dist/esm-node/build-config/build-environment.mjs +26 -160
  112. package/dist/esm-node/build-config/internal-effect-discovery.mjs +103 -0
  113. package/dist/esm-node/build-config/public.mjs +1 -1
  114. package/dist/esm-node/build-config/remote-address.mjs +21 -0
  115. package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
  116. package/dist/esm-node/cloudflare/delivery-unit.mjs +124 -45
  117. package/dist/esm-node/cloudflare/index.mjs +15 -5
  118. package/dist/esm-node/cloudflare/native-renderer.mjs +49 -0
  119. package/dist/esm-node/cloudflare/renderer-identity.mjs +58 -0
  120. package/dist/esm-node/cloudflare/worker-manifest.mjs +55 -14
  121. package/dist/esm-node/cloudflare/worker-options.mjs +11 -0
  122. package/dist/esm-node/cloudflare/wrangler-config.mjs +203 -12
  123. package/dist/esm-node/cloudflare-builder.mjs +301 -89
  124. package/dist/esm-node/cloudflare-output-contract.mjs +29 -3
  125. package/dist/esm-node/cloudflare-output-verifier/identity.mjs +113 -24
  126. package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
  127. package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
  128. package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
  129. package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
  130. package/dist/esm-node/config.mjs +3 -1
  131. package/dist/esm-node/deploy-output/npmAliases.mjs +92 -6
  132. package/dist/esm-node/deploy-output/plugin.mjs +24 -6
  133. package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
  134. package/dist/esm-node/deploy-output/target.mjs +50 -3
  135. package/dist/esm-node/deploy-output/trace-files.mjs +48 -0
  136. package/dist/esm-node/module-federation-config/exposes.mjs +19 -0
  137. package/dist/esm-node/module-federation-config/index.mjs +5 -0
  138. package/dist/esm-node/module-federation-config/inspect.mjs +66 -0
  139. package/dist/esm-node/module-federation-config/object-literal.mjs +44 -0
  140. package/dist/esm-node/module-federation-config/syntax.mjs +102 -0
  141. package/dist/esm-node/module-federation-config/types.mjs +1 -0
  142. package/dist/esm-node/native-type-checker.mjs +158 -18
  143. package/dist/esm-node/policy-defaults.mjs +52 -0
  144. package/dist/esm-node/release-envelope/canonical.mjs +2 -9
  145. package/dist/esm-node/release-envelope/contract.mjs +22 -0
  146. package/dist/esm-node/release-envelope/framework-output.mjs +297 -106
  147. package/dist/esm-node/release-envelope/index.mjs +149 -38
  148. package/dist/esm-node/release-envelope/plugin.mjs +24 -15
  149. package/dist/esm-node/release-envelope/renderer-output-stamp.mjs +113 -0
  150. package/dist/esm-node/release-envelope/types.mjs +2 -3
  151. package/dist/esm-node/release-identity.mjs +8 -5
  152. package/dist/esm-node/renderer-build-identity.mjs +83 -0
  153. package/dist/esm-node/runtime-package-resolution.mjs +61 -0
  154. package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  155. package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  156. package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  157. package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  158. package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  159. package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
  160. package/dist/esm-node/workspace-topology.mjs +22 -0
  161. package/dist/types/backend-federation-build/codegen.d.ts +30 -0
  162. package/dist/types/backend-federation-build/config.d.ts +10 -57
  163. package/dist/types/backend-federation-build/emit.d.ts +12 -2
  164. package/dist/types/backend-federation-build/index.d.ts +1 -1
  165. package/dist/types/build-config/build-environment.d.ts +1 -9
  166. package/dist/types/build-config/internal-effect-discovery.d.ts +4 -0
  167. package/dist/types/build-config/public.d.ts +1 -1
  168. package/dist/types/build-config/remote-address.d.ts +3 -0
  169. package/dist/types/cloudflare/artifacts.d.ts +7 -6
  170. package/dist/types/cloudflare/constants.d.ts +1 -1
  171. package/dist/types/cloudflare/delivery-unit.d.ts +38 -7
  172. package/dist/types/cloudflare/native-renderer.d.ts +30 -0
  173. package/dist/types/cloudflare/renderer-identity.d.ts +25 -0
  174. package/dist/types/cloudflare/worker-manifest.d.ts +17 -2
  175. package/dist/types/cloudflare/worker-options.d.ts +20 -0
  176. package/dist/types/cloudflare/wrangler-config.d.ts +55 -2
  177. package/dist/types/cloudflare-builder.d.ts +51 -8
  178. package/dist/types/cloudflare-output-contract.d.ts +5 -3
  179. package/dist/types/cloudflare-output-verifier/identity.d.ts +7 -3
  180. package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
  181. package/dist/types/config.d.ts +98 -13
  182. package/dist/types/deploy-output/plugin.d.ts +29 -9
  183. package/dist/types/deploy-output/public-assets.d.ts +51 -0
  184. package/dist/types/deploy-output/target.d.ts +28 -5
  185. package/dist/types/deploy-output/trace-files.d.ts +17 -0
  186. package/dist/types/module-federation-config/exposes.d.ts +11 -0
  187. package/dist/types/module-federation-config/index.d.ts +5 -0
  188. package/dist/types/module-federation-config/inspect.d.ts +2 -0
  189. package/dist/types/module-federation-config/object-literal.d.ts +4 -0
  190. package/dist/types/module-federation-config/syntax.d.ts +7 -0
  191. package/dist/types/module-federation-config/types.d.ts +20 -0
  192. package/dist/types/native-type-checker.d.ts +26 -3
  193. package/dist/types/policy-defaults.d.ts +56 -0
  194. package/dist/types/release-envelope/canonical.d.ts +5 -5
  195. package/dist/types/release-envelope/contract.d.mts +8 -0
  196. package/dist/types/release-envelope/framework-output.d.ts +23 -13
  197. package/dist/types/release-envelope/index.d.ts +17 -5
  198. package/dist/types/release-envelope/plugin.d.ts +10 -6
  199. package/dist/types/release-envelope/renderer-output-stamp.d.ts +34 -0
  200. package/dist/types/release-envelope/types.d.ts +42 -4
  201. package/dist/types/release-identity.d.ts +3 -2
  202. package/dist/types/renderer-build-identity.d.ts +30 -0
  203. package/dist/types/runtime-package-resolution.d.ts +28 -0
  204. package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
  205. package/dist/types/workspace-topology.d.ts +91 -0
  206. package/package.json +97 -26
  207. package/src/backend-federation-build/codegen.ts +28 -19
  208. package/src/backend-federation-build/config.ts +189 -123
  209. package/src/backend-federation-build/emit.ts +119 -38
  210. package/src/backend-federation-build/index.ts +4 -1
  211. package/src/build-config/build-environment.ts +59 -285
  212. package/src/build-config/internal-effect-discovery.ts +239 -0
  213. package/src/build-config/public.ts +0 -1
  214. package/src/build-config/remote-address.ts +33 -0
  215. package/src/cloudflare/artifacts.ts +22 -43
  216. package/src/cloudflare/delivery-unit.ts +283 -60
  217. package/src/cloudflare/index.ts +35 -19
  218. package/src/cloudflare/native-renderer.ts +117 -0
  219. package/src/cloudflare/renderer-identity.ts +171 -0
  220. package/src/cloudflare/worker-manifest.ts +108 -10
  221. package/src/cloudflare/worker-options.ts +60 -0
  222. package/src/cloudflare/wrangler-config.ts +390 -14
  223. package/src/cloudflare-builder.ts +543 -170
  224. package/src/cloudflare-output-contract.ts +45 -2
  225. package/src/cloudflare-output-verifier/identity.ts +138 -7
  226. package/src/cloudflare-output-verifier/index.ts +20 -0
  227. package/src/cloudflare-output-verifier/manifest.ts +77 -0
  228. package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
  229. package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
  230. package/src/config.ts +101 -19
  231. package/src/deploy-output/npmAliases.ts +141 -4
  232. package/src/deploy-output/plugin.ts +55 -18
  233. package/src/deploy-output/public-assets.ts +224 -0
  234. package/src/deploy-output/target.ts +97 -14
  235. package/src/deploy-output/trace-files.ts +80 -0
  236. package/src/module-federation-config/exposes.ts +46 -0
  237. package/src/module-federation-config/index.ts +22 -0
  238. package/src/module-federation-config/inspect.ts +157 -0
  239. package/src/module-federation-config/object-literal.ts +76 -0
  240. package/src/module-federation-config/syntax.ts +212 -0
  241. package/src/module-federation-config/types.ts +22 -0
  242. package/src/native-type-checker.ts +281 -26
  243. package/src/policy-defaults.ts +157 -0
  244. package/src/release-envelope/canonical.ts +6 -18
  245. package/src/release-envelope/contract.mjs +28 -0
  246. package/src/release-envelope/framework-output.ts +681 -156
  247. package/src/release-envelope/index.ts +315 -60
  248. package/src/release-envelope/plugin.ts +54 -38
  249. package/src/release-envelope/renderer-output-stamp.ts +210 -0
  250. package/src/release-envelope/types.ts +75 -6
  251. package/src/release-identity.ts +19 -2
  252. package/src/renderer-build-identity.ts +168 -0
  253. package/src/runtime-package-resolution.ts +115 -0
  254. package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
  255. package/src/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
  256. package/src/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
  257. package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
  258. package/src/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
  259. package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
  260. package/src/workspace-topology.ts +125 -0
  261. package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +0 -11
  262. package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +0 -281
  263. package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  264. package/dist/cjs/templates/cloudflare-worker-path.mjs +0 -98
  265. package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +0 -11
  266. package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +0 -281
  267. package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  268. package/dist/esm/templates/cloudflare-worker-path.mjs +0 -98
  269. package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +0 -11
  270. package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +0 -281
  271. package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  272. package/dist/esm-node/templates/cloudflare-worker-path.mjs +0 -98
  273. package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +0 -5
  274. package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +0 -48
  275. package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +0 -13
  276. package/dist/types/templates/cloudflare-worker-path.d.mts +0 -21
  277. package/src/templates/cloudflare-worker-fs-promises.mjs +0 -11
  278. package/src/templates/cloudflare-worker-loadable-server.mjs +0 -281
  279. package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
  280. package/src/templates/cloudflare-worker-path.mjs +0 -98
@@ -0,0 +1,210 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs/promises';
3
+ import path from 'node:path';
4
+ import { isDeepStrictEqual } from 'node:util';
5
+ import {
6
+ assertUltramodernBuildArtifact,
7
+ formatBackendFederationValidationErrors,
8
+ stampUltramodernBuildArtifactIdentity,
9
+ ULTRAMODERN_BUILD_ARTIFACT_FILE,
10
+ ULTRAMODERN_BUILD_ARTIFACT_PATH,
11
+ type UltramodernBuildArtifact,
12
+ type UltramodernBuildUiSurface,
13
+ validateRendererIdentity,
14
+ validateRendererProfile,
15
+ validateRendererRouterBindings,
16
+ } from '@modern-js/backend-federation-contracts';
17
+
18
+ export type RendererBuildOutputContext = {
19
+ appDirectory: string;
20
+ distDirectory: string;
21
+ entrypoints: readonly { entryName: string; isMainEntry?: boolean }[];
22
+ };
23
+
24
+ export type FinalizedRendererBuildOutput = {
25
+ buildMarker: string;
26
+ sourceRevision: string;
27
+ ui: Pick<
28
+ UltramodernBuildUiSurface,
29
+ 'rendererIdentity' | 'rendererProfile' | 'routerBindings'
30
+ >;
31
+ };
32
+
33
+ export type RendererBuildOutputOptions = {
34
+ resolveRendererBuild: (
35
+ context: RendererBuildOutputContext,
36
+ ) => Promise<FinalizedRendererBuildOutput>;
37
+ /** Exact metadata producer selected by the owning renderer composition. */
38
+ rendererBuildPlugin: string;
39
+ };
40
+
41
+ export const validateFinalizedRendererBuild = (
42
+ output: FinalizedRendererBuildOutput,
43
+ context: RendererBuildOutputContext,
44
+ appId: string,
45
+ ) => {
46
+ const entryNames = context.entrypoints.map(entry => entry.entryName);
47
+ const primary =
48
+ context.entrypoints.find(entry => entry.isMainEntry) ??
49
+ context.entrypoints[0];
50
+ const errors = [
51
+ ...validateRendererIdentity(output.ui.rendererIdentity).errors,
52
+ ...validateRendererProfile(output.ui.rendererProfile).errors,
53
+ ...validateRendererRouterBindings(
54
+ output.ui.routerBindings,
55
+ entryNames,
56
+ 'routerBindings',
57
+ ).errors,
58
+ ];
59
+ if (errors.length)
60
+ throw new Error(formatBackendFederationValidationErrors(errors));
61
+ if (
62
+ !primary ||
63
+ new Set(entryNames).size !== entryNames.length ||
64
+ context.entrypoints.filter(entry => entry.isMainEntry).length > 1 ||
65
+ output.ui.rendererIdentity.entryName !== primary.entryName ||
66
+ output.ui.rendererIdentity.appId !== appId ||
67
+ output.ui.rendererIdentity.buildId !== output.buildMarker ||
68
+ output.ui.rendererIdentity.renderer !== output.ui.rendererProfile.renderer
69
+ )
70
+ throw new Error(
71
+ 'Finalized renderer output conflicts with the actual application entries and release identity.',
72
+ );
73
+ };
74
+
75
+ export const stampFinalizedRendererBuildArtifact = (
76
+ artifact: UltramodernBuildArtifact,
77
+ output: FinalizedRendererBuildOutput,
78
+ context: RendererBuildOutputContext,
79
+ ) => {
80
+ assertUltramodernBuildArtifact(artifact);
81
+ const ui = artifact.surfaces.ui;
82
+ if (!ui)
83
+ throw new Error(
84
+ 'Finalized renderer output requires a declared UI artifact',
85
+ );
86
+ validateFinalizedRendererBuild(output, context, artifact.deliveryUnit.appId);
87
+ const drifted = [
88
+ !isDeepStrictEqual(
89
+ { ...ui.rendererIdentity, buildId: output.ui.rendererIdentity.buildId },
90
+ output.ui.rendererIdentity,
91
+ ) &&
92
+ `rendererIdentity (captured entry ${ui.rendererIdentity.entryName}, built entry ${output.ui.rendererIdentity.entryName})`,
93
+ !isDeepStrictEqual(ui.rendererProfile, output.ui.rendererProfile) &&
94
+ 'rendererProfile',
95
+ !isDeepStrictEqual(ui.routerBindings, output.ui.routerBindings) &&
96
+ `routerBindings (captured entries ${Object.keys(ui.routerBindings).join(', ')}, built entries ${Object.keys(output.ui.routerBindings).join(', ')})`,
97
+ ].filter(Boolean);
98
+ if (drifted.length)
99
+ throw new Error(
100
+ `Finalized renderer output conflicts with the captured application profile or router bindings: ${drifted.join('; ')} differ from ${ULTRAMODERN_BUILD_ARTIFACT_PATH}. After changing modern.config entries, renderer or router, recapture it with \`ultramodern-create ultramodern sync-delivery-unit\`.`,
101
+ );
102
+ const identity = {
103
+ buildMarker: output.buildMarker,
104
+ sourceRevision: output.sourceRevision,
105
+ };
106
+ const stamped = stampUltramodernBuildArtifactIdentity(artifact, identity);
107
+ return stampUltramodernBuildArtifactIdentity(
108
+ {
109
+ ...stamped,
110
+ surfaces: {
111
+ ...stamped.surfaces,
112
+ ui: { ...stamped.surfaces.ui!, ...output.ui },
113
+ },
114
+ },
115
+ identity,
116
+ );
117
+ };
118
+
119
+ async function writeOwnedArtifact(file: string, bytes: string) {
120
+ await fs.mkdir(path.dirname(file), { recursive: true });
121
+ const temporary = `${file}.${randomUUID()}.tmp`;
122
+ let created = false;
123
+ try {
124
+ const handle = await fs.open(temporary, 'wx');
125
+ created = true;
126
+ try {
127
+ await handle.writeFile(bytes);
128
+ } finally {
129
+ await handle.close();
130
+ }
131
+ await fs.rename(temporary, file);
132
+ } finally {
133
+ if (created) await fs.rm(temporary, { force: true });
134
+ }
135
+ }
136
+
137
+ export async function emitRendererBuildArtifact(
138
+ context: RendererBuildOutputContext,
139
+ options: RendererBuildOutputOptions,
140
+ ) {
141
+ const source = path.join(
142
+ context.appDirectory,
143
+ ULTRAMODERN_BUILD_ARTIFACT_PATH,
144
+ );
145
+ const bytes = await fs.readFile(source).catch(error => {
146
+ if (error.code === 'ENOENT') return undefined;
147
+ throw error;
148
+ });
149
+ if (!bytes) {
150
+ const declaration = await fs
151
+ .stat(source.replace(/\.json$/u, '.ts'))
152
+ .catch(error => {
153
+ if (error.code === 'ENOENT') return undefined;
154
+ throw error;
155
+ });
156
+ if (declaration)
157
+ throw new Error(
158
+ 'Generated UI artifact declaration is missing its build carrier',
159
+ );
160
+ return undefined;
161
+ }
162
+ const artifact: unknown = JSON.parse(bytes.toString('utf8'));
163
+ assertUltramodernBuildArtifact(artifact);
164
+ const finalized = await options.resolveRendererBuild(context);
165
+ const stamped = stampFinalizedRendererBuildArtifact(
166
+ artifact,
167
+ finalized,
168
+ context,
169
+ );
170
+ const serialized = `${JSON.stringify(stamped, null, 2)}\n`;
171
+ for (const directory of [
172
+ context.distDirectory,
173
+ path.join(context.distDirectory, 'public'),
174
+ ])
175
+ await writeOwnedArtifact(
176
+ path.join(directory, ULTRAMODERN_BUILD_ARTIFACT_FILE),
177
+ serialized,
178
+ );
179
+ return stamped;
180
+ }
181
+
182
+ /** Stamp generated UI carriers after their actual compiler manifest is committed. */
183
+ export function rendererBuildArtifactStampPlugin(
184
+ options: RendererBuildOutputOptions,
185
+ ) {
186
+ if (
187
+ typeof options.rendererBuildPlugin !== 'string' ||
188
+ !options.rendererBuildPlugin ||
189
+ options.rendererBuildPlugin.trim() !== options.rendererBuildPlugin ||
190
+ typeof options.resolveRendererBuild !== 'function'
191
+ )
192
+ throw new Error(
193
+ 'Renderer artifact stamping requires its owning metadata producer',
194
+ );
195
+ return {
196
+ name: '@modern-js/renderer-build-artifact-stamp',
197
+ pre: [options.rendererBuildPlugin],
198
+ required: [options.rendererBuildPlugin],
199
+ setup(api: {
200
+ getAppContext(): RendererBuildOutputContext & { apiOnly?: boolean };
201
+ onAfterBuild(handler: () => Promise<void>): void;
202
+ }) {
203
+ api.onAfterBuild(async () => {
204
+ const context = api.getAppContext();
205
+ if (context.apiOnly) return;
206
+ await emitRendererBuildArtifact(context, options);
207
+ });
208
+ },
209
+ };
210
+ }
@@ -1,7 +1,24 @@
1
- export const MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION = 3 as const;
1
+ import type {
2
+ RendererIdentity,
3
+ RendererProfile,
4
+ RendererRouterBindings,
5
+ } from '@modern-js/backend-federation-contracts';
2
6
 
3
- export const MICROVERTICAL_RELEASE_ENVELOPE_KIND =
4
- 'ultramodern-target-microvertical-release-envelope' as const;
7
+ import {
8
+ MICROVERTICAL_RELEASE_ENVELOPE_KIND,
9
+ MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION,
10
+ SHELL_RELEASE_ENVELOPE_KIND,
11
+ } from './contract.mjs';
12
+
13
+ export {
14
+ MICROVERTICAL_RELEASE_ENVELOPE_KIND,
15
+ MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION,
16
+ SHELL_RELEASE_ENVELOPE_KIND,
17
+ };
18
+
19
+ export type ReleaseEnvelopeKind =
20
+ | typeof MICROVERTICAL_RELEASE_ENVELOPE_KIND
21
+ | typeof SHELL_RELEASE_ENVELOPE_KIND;
5
22
 
6
23
  export const MICROVERTICAL_RELEASE_TARGETS = ['node', 'cloudflare'] as const;
7
24
 
@@ -48,17 +65,35 @@ export type MicroVerticalReleaseSurfaces = {
48
65
  uiClient: string[];
49
66
  ssr: string[];
50
67
  apiBackend: string[];
51
- backendFederation: {
68
+ backendFederation?: {
52
69
  manifest: string;
53
70
  container: string;
54
71
  };
55
72
  };
56
73
 
74
+ export type MicroVerticalReleaseUi = {
75
+ rendererIdentity: RendererIdentity;
76
+ rendererProfile: RendererProfile;
77
+ routerBindings: RendererRouterBindings;
78
+ };
79
+
80
+ /** A Shell consumes remotes; it never publishes a backend federation pair. */
81
+ export type ShellReleaseSurfaces = {
82
+ uiClient: string[];
83
+ ssr: string[];
84
+ apiBackend: string[];
85
+ };
86
+
87
+ export type ReleaseSurfaces =
88
+ | MicroVerticalReleaseSurfaces
89
+ | ShellReleaseSurfaces;
90
+
57
91
  export type MicroVerticalReleaseEnvelopePayload = {
58
92
  schemaVersion: typeof MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION;
59
93
  kind: typeof MICROVERTICAL_RELEASE_ENVELOPE_KIND;
60
94
  target: MicroVerticalReleaseTarget;
61
95
  identity: MicroVerticalReleaseIdentity;
96
+ ui?: MicroVerticalReleaseUi;
62
97
  artifacts: MicroVerticalReleaseArtifact[];
63
98
  surfaces: MicroVerticalReleaseSurfaces;
64
99
  };
@@ -68,16 +103,50 @@ export type MicroVerticalReleaseEnvelope =
68
103
  envelopeDigest: string;
69
104
  };
70
105
 
106
+ export type ShellReleaseEnvelopePayload = {
107
+ schemaVersion: typeof MICROVERTICAL_RELEASE_ENVELOPE_SCHEMA_VERSION;
108
+ kind: typeof SHELL_RELEASE_ENVELOPE_KIND;
109
+ target: MicroVerticalReleaseTarget;
110
+ identity: MicroVerticalReleaseIdentity;
111
+ ui?: MicroVerticalReleaseUi;
112
+ artifacts: MicroVerticalReleaseArtifact[];
113
+ surfaces: ShellReleaseSurfaces;
114
+ };
115
+
116
+ export type ShellReleaseEnvelope = ShellReleaseEnvelopePayload & {
117
+ envelopeDigest: string;
118
+ };
119
+
120
+ export type ReleaseEnvelopePayload =
121
+ | MicroVerticalReleaseEnvelopePayload
122
+ | ShellReleaseEnvelopePayload;
123
+
124
+ export type ReleaseEnvelope =
125
+ | MicroVerticalReleaseEnvelope
126
+ | ShellReleaseEnvelope;
127
+
71
128
  export type CreateMicroVerticalReleaseEnvelopeInput = {
72
129
  artifactRoot: string;
73
130
  target: MicroVerticalReleaseTarget;
74
131
  identity: MicroVerticalReleaseIdentity;
132
+ ui?: MicroVerticalReleaseUi;
75
133
  artifacts: MicroVerticalReleaseArtifactInput[];
76
- surfaces: MicroVerticalReleaseSurfaces;
77
- };
134
+ } & (
135
+ | {
136
+ kind?: typeof MICROVERTICAL_RELEASE_ENVELOPE_KIND;
137
+ surfaces: MicroVerticalReleaseSurfaces;
138
+ }
139
+ | {
140
+ kind: typeof SHELL_RELEASE_ENVELOPE_KIND;
141
+ surfaces: ShellReleaseSurfaces;
142
+ }
143
+ );
78
144
 
79
145
  export type VerifyMicroVerticalReleaseEnvelopeOptions = {
80
146
  artifactRoot: string;
81
147
  logicalPathForArtifact?: (artifact: MicroVerticalReleaseArtifact) => string;
82
148
  expectedTarget?: MicroVerticalReleaseTarget;
149
+ expectedKind?: ReleaseEnvelopeKind;
150
+ expectedRendererIdentity?: RendererIdentity;
151
+ expectedRendererProfile?: RendererProfile;
83
152
  };
@@ -1,6 +1,9 @@
1
1
  import { execFileSync } from 'node:child_process';
2
2
  import { createHash } from 'node:crypto';
3
3
 
4
+ // The framework initializes this fixed reader before authored config observation.
5
+ // Its commands remain private; authored subprocess APIs keep their own guards.
6
+ const invokeGit = execFileSync;
4
7
  const BUILD_MARKER_NAMESPACE =
5
8
  'ultramodern-delivery-unit-release-build-marker:v1';
6
9
  const PROMOTABLE_SOURCE_REVISION_PATTERN = /^(?:[a-f0-9]{40}|[a-f0-9]{64})$/u;
@@ -10,14 +13,24 @@ export type UltramodernReleaseIdentity = {
10
13
  sourceRevision: string;
11
14
  };
12
15
 
16
+ // Inherited GIT_* variables (git hooks export GIT_DIR and GIT_INDEX_FILE)
17
+ // would make git describe some other repository than `workspaceRoot`.
18
+ const workspaceGitEnv = (): NodeJS.ProcessEnv =>
19
+ Object.fromEntries(
20
+ Object.entries(process.env).filter(
21
+ ([name]) => !name.toUpperCase().startsWith('GIT_'),
22
+ ),
23
+ );
24
+
13
25
  const gitOutput = (
14
26
  workspaceRoot: string,
15
27
  args: string[],
16
28
  ): string | undefined => {
17
29
  try {
18
- return execFileSync('git', args, {
30
+ return invokeGit('git', args, {
19
31
  cwd: workspaceRoot,
20
32
  encoding: 'utf8',
33
+ env: workspaceGitEnv(),
21
34
  stdio: ['ignore', 'pipe', 'ignore'],
22
35
  }).trim();
23
36
  } catch {
@@ -44,10 +57,11 @@ export const createUltramodernReleaseBuildMarker = ({
44
57
  export const resolveUltramodernSourceRevision = (
45
58
  workspaceRoot = process.cwd(),
46
59
  configuredSourceRevision?: string,
60
+ environment: Readonly<NodeJS.ProcessEnv> = process.env,
47
61
  ): string => {
48
62
  const configuredArgumentValue = configuredSourceRevision?.trim();
49
63
  const configuredEnvironmentValue =
50
- process.env.ULTRAMODERN_SOURCE_REVISION?.trim();
64
+ environment.ULTRAMODERN_SOURCE_REVISION?.trim();
51
65
  const configuredArgument =
52
66
  configuredArgumentValue && configuredArgumentValue !== 'workspace'
53
67
  ? configuredArgumentValue
@@ -110,16 +124,19 @@ export const resolveUltramodernReleaseIdentity = ({
110
124
  sourceRevision: configuredSourceRevision,
111
125
  unitId,
112
126
  workspaceRoot,
127
+ environment,
113
128
  }: {
114
129
  generationBuildMarker: string;
115
130
  sourceRevision?: string;
116
131
  unitId: string;
117
132
  workspaceRoot?: string;
133
+ environment?: Readonly<NodeJS.ProcessEnv>;
118
134
  }): UltramodernReleaseIdentity => {
119
135
  const explicitSourceRevision = configuredSourceRevision?.trim();
120
136
  const sourceRevision = resolveUltramodernSourceRevision(
121
137
  workspaceRoot,
122
138
  explicitSourceRevision,
139
+ environment,
123
140
  );
124
141
  return {
125
142
  buildMarker:
@@ -0,0 +1,168 @@
1
+ import { createHash, randomUUID } from 'node:crypto';
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+ import {
5
+ immutableRendererRouterBindings,
6
+ type RendererIdentity,
7
+ type RendererName,
8
+ type RendererProfile,
9
+ type RendererRouterBindings,
10
+ type RouterFramework,
11
+ validateRendererProfile,
12
+ validateRendererRouterBindings,
13
+ } from '@modern-js/backend-federation-contracts';
14
+ import { resolveUltramodernSourceRevision } from './release-identity';
15
+
16
+ export type RendererProfileKeyInput = RendererProfile & {
17
+ readonly dependencies?: Readonly<Record<string, string>>;
18
+ };
19
+
20
+ export interface RendererBuildIdentityOptions {
21
+ projectRoot: string;
22
+ renderer: RendererName;
23
+ profile: RendererProfileKeyInput;
24
+ entryNames: readonly string[];
25
+ mode: 'development' | 'production';
26
+ /** Delivery-unit app id; defaults to the application package name. */
27
+ appId?: string;
28
+ /** Externally authenticated source revision (delivery unit or CI). */
29
+ sourceRevision?: string;
30
+ /** Actual final entry ownership, supplied by the selected renderer composition. */
31
+ routerBindings: RendererRouterBindings;
32
+ /** Admitted router frameworks, supplied by the selected renderer owner. */
33
+ routerFrameworks?: readonly RouterFramework[];
34
+ }
35
+
36
+ export interface RendererBuildIdentities {
37
+ readonly identities: Readonly<Record<string, RendererIdentity>>;
38
+ readonly buildId: string;
39
+ readonly profileKey: string;
40
+ readonly sourceRevision: string;
41
+ readonly routerBindings: RendererRouterBindings;
42
+ }
43
+
44
+ const LOCKFILES = [
45
+ 'pnpm-lock.yaml',
46
+ 'package-lock.json',
47
+ 'yarn.lock',
48
+ 'bun.lock',
49
+ 'bun.lockb',
50
+ ];
51
+
52
+ /** Stable per process: one dev session or dirty build keeps one buildId. */
53
+ const processNonce = randomUUID();
54
+
55
+ const sha256 = (value: string | Uint8Array) =>
56
+ createHash('sha256').update(value).digest('hex');
57
+
58
+ /** Renderer, protocol and installed renderer/framework/compiler versions. */
59
+ export function rendererProfileKey(profile: RendererProfileKeyInput): string {
60
+ return sha256(
61
+ JSON.stringify([
62
+ profile.renderer,
63
+ profile.protocolVersion,
64
+ profile.compiler,
65
+ profile.hydration,
66
+ profile.router,
67
+ Object.entries(profile.dependencies ?? {}).sort(([left], [right]) =>
68
+ left < right ? -1 : left > right ? 1 : 0,
69
+ ),
70
+ ]),
71
+ );
72
+ }
73
+
74
+ /** Hash of the nearest lockfile, or an empty marker when there is none. */
75
+ function lockfileHash(projectRoot: string): string {
76
+ let directory = path.resolve(projectRoot);
77
+ for (;;) {
78
+ for (const name of LOCKFILES) {
79
+ try {
80
+ return sha256(fs.readFileSync(path.join(directory, name)));
81
+ } catch (error) {
82
+ if ((error as NodeJS.ErrnoException).code !== 'ENOENT') throw error;
83
+ }
84
+ }
85
+ const parent = path.dirname(directory);
86
+ if (parent === directory) return '';
87
+ directory = parent;
88
+ }
89
+ }
90
+
91
+ /** Resolve once per build before emitting entries; never rehashes sources. */
92
+ export function resolveRendererBuildIdentities(
93
+ options: RendererBuildIdentityOptions,
94
+ ): RendererBuildIdentities {
95
+ const profile: RendererProfile = {
96
+ renderer: options.profile.renderer,
97
+ protocolVersion: options.profile.protocolVersion,
98
+ compiler: options.profile.compiler,
99
+ hydration: options.profile.hydration,
100
+ router: options.profile.router,
101
+ };
102
+ const validation = validateRendererProfile(profile);
103
+ if (!validation.ok || options.renderer !== profile.renderer)
104
+ throw new Error(
105
+ `Renderer/profile identity mismatch: ${JSON.stringify(validation.errors)}.`,
106
+ );
107
+ const entries = [...options.entryNames].sort();
108
+ if (
109
+ entries.length === 0 ||
110
+ new Set(entries).size !== entries.length ||
111
+ entries.some(value => !value || value === '__proto__')
112
+ )
113
+ throw new Error(
114
+ 'Renderer build identity requires unique nonempty entry names.',
115
+ );
116
+ const routerValidation = validateRendererRouterBindings(
117
+ options.routerBindings,
118
+ entries,
119
+ 'routerBindings',
120
+ options.routerFrameworks,
121
+ );
122
+ if (!routerValidation.ok)
123
+ throw new Error(
124
+ `Invalid renderer router bindings: ${JSON.stringify(routerValidation.errors)}.`,
125
+ );
126
+ const projectRoot = path.resolve(options.projectRoot);
127
+ const appId =
128
+ options.appId ??
129
+ JSON.parse(fs.readFileSync(path.join(projectRoot, 'package.json'), 'utf8'))
130
+ .name;
131
+ if (typeof appId !== 'string' || !appId || appId.trim() !== appId)
132
+ throw new Error(
133
+ 'Renderer application identity requires a delivery artifact appId or a package name.',
134
+ );
135
+ const profileKey = rendererProfileKey(options.profile);
136
+ const sourceRevision = resolveUltramodernSourceRevision(
137
+ projectRoot,
138
+ options.sourceRevision,
139
+ );
140
+ const buildId = sha256(
141
+ JSON.stringify([
142
+ profileKey,
143
+ lockfileHash(projectRoot),
144
+ options.mode === 'development' || sourceRevision === 'workspace'
145
+ ? processNonce
146
+ : sourceRevision,
147
+ ]),
148
+ );
149
+ const identities = Object.fromEntries(
150
+ entries.map(entryName => [
151
+ entryName,
152
+ Object.freeze({
153
+ renderer: options.renderer,
154
+ appId,
155
+ entryName,
156
+ protocolVersion: 1 as const,
157
+ buildId,
158
+ }),
159
+ ]),
160
+ );
161
+ return Object.freeze({
162
+ identities: Object.freeze(identities),
163
+ buildId,
164
+ profileKey,
165
+ sourceRevision,
166
+ routerBindings: immutableRendererRouterBindings(options.routerBindings),
167
+ });
168
+ }
@@ -0,0 +1,115 @@
1
+ import fs from 'node:fs';
2
+ import { createRequire } from 'node:module';
3
+ import path from 'node:path';
4
+ import { fileURLToPath } from 'node:url';
5
+ import type { RsbuildPlugin, Rspack } from '@rsbuild/core';
6
+
7
+ /**
8
+ * A generated `runtime-register.js` lives inside the *application*, so every
9
+ * runtime plugin it imports has to resolve from the application's own
10
+ * `node_modules`. Packages registered by a CLI plugin are dependencies of that
11
+ * CLI plugin, not of the application, and under an isolated (pnpm) linker the
12
+ * application cannot see them — the build fails with `Module not found`.
13
+ *
14
+ * Instead of freezing a single resolved file (which would lock one export
15
+ * condition and break the web/node split), the registering package contributes
16
+ * the `node_modules` directory that actually hosts the package as a *fallback*
17
+ * module directory. Bare specifiers keep their normal `exports` resolution, the
18
+ * application's own copy still wins when it declares the dependency, and both
19
+ * the workspace source layout and a published pnpm layout work.
20
+ */
21
+ export const findHostingModuleDirectory = (
22
+ packageName: string,
23
+ fromDirectory: string,
24
+ ): string | undefined => {
25
+ let directory = path.resolve(fromDirectory);
26
+ for (;;) {
27
+ const candidate = path.join(directory, 'node_modules');
28
+ if (fs.existsSync(path.join(candidate, packageName))) {
29
+ return candidate;
30
+ }
31
+ const parent = path.dirname(directory);
32
+ if (parent === directory) {
33
+ return undefined;
34
+ }
35
+ directory = parent;
36
+ }
37
+ };
38
+
39
+ /**
40
+ * Collect the fallback module directories for the given packages, starting the
41
+ * search at the module that registers them.
42
+ */
43
+ export const collectRuntimePackageModuleDirectories = (
44
+ packageNames: readonly string[],
45
+ registrarUrl: string,
46
+ ): string[] => {
47
+ const registrarDirectory = path.dirname(fileURLToPath(registrarUrl));
48
+ const directories: string[] = [];
49
+ for (const packageName of packageNames) {
50
+ const directory = findHostingModuleDirectory(
51
+ packageName,
52
+ registrarDirectory,
53
+ );
54
+ if (directory && !directories.includes(directory)) {
55
+ directories.push(directory);
56
+ }
57
+ }
58
+ return directories;
59
+ };
60
+
61
+ const appendModuleDirectories = (
62
+ config: Rspack.Configuration,
63
+ directories: readonly string[],
64
+ ) => {
65
+ if (directories.length === 0) {
66
+ return;
67
+ }
68
+ config.resolve ??= {};
69
+ const existing = config.resolve.modules ?? ['node_modules'];
70
+ // Appended, never prepended: the application's own upward `node_modules`
71
+ // search keeps priority, so a declared dependency still wins.
72
+ config.resolve.modules = [
73
+ ...existing,
74
+ ...directories.filter(directory => !existing.includes(directory)),
75
+ ];
76
+ };
77
+
78
+ /**
79
+ * Rsbuild plugin that adds the given directories as fallback module roots for
80
+ * every environment (web, node and worker bundles alike).
81
+ */
82
+ export const createRuntimePackageResolutionPlugin = (
83
+ directories: readonly string[],
84
+ ): RsbuildPlugin => ({
85
+ name: 'ultramodern:runtime-package-resolution',
86
+ setup(api) {
87
+ api.modifyRspackConfig({
88
+ order: 'post',
89
+ handler(config) {
90
+ appendModuleDirectories(config, directories);
91
+ },
92
+ });
93
+ api.onBeforeCreateCompiler({
94
+ order: 'post',
95
+ handler({ bundlerConfigs }) {
96
+ for (const config of bundlerConfigs) {
97
+ appendModuleDirectories(config, directories);
98
+ }
99
+ },
100
+ });
101
+ },
102
+ });
103
+
104
+ /** `true` when the package resolves from the registering module at all. */
105
+ export const canResolveRuntimePackage = (
106
+ packageName: string,
107
+ registrarUrl: string,
108
+ ): boolean => {
109
+ try {
110
+ createRequire(registrarUrl).resolve(packageName);
111
+ return true;
112
+ } catch {
113
+ return false;
114
+ }
115
+ };