@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.
- package/README.md +23 -0
- package/dist/cjs/backend-federation-build/codegen.js +29 -18
- package/dist/cjs/backend-federation-build/config.js +84 -45
- package/dist/cjs/backend-federation-build/emit.js +63 -22
- package/dist/cjs/build-config/build-environment.js +25 -162
- package/dist/cjs/build-config/internal-effect-discovery.js +143 -0
- package/dist/cjs/build-config/public.js +2 -5
- package/dist/cjs/build-config/remote-address.js +58 -0
- package/dist/cjs/cloudflare/artifacts.js +8 -19
- package/dist/cjs/cloudflare/delivery-unit.js +125 -43
- package/dist/cjs/cloudflare/index.js +14 -4
- package/dist/cjs/cloudflare/native-renderer.js +100 -0
- package/dist/cjs/cloudflare/renderer-identity.js +106 -0
- package/dist/cjs/cloudflare/worker-manifest.js +54 -13
- package/dist/cjs/cloudflare/worker-options.js +48 -0
- package/dist/cjs/cloudflare/wrangler-config.js +210 -13
- package/dist/cjs/cloudflare-builder.js +307 -89
- package/dist/cjs/cloudflare-output-contract.js +36 -4
- package/dist/cjs/cloudflare-output-verifier/identity.js +112 -23
- package/dist/cjs/cloudflare-output-verifier/index.js +10 -0
- package/dist/cjs/cloudflare-output-verifier/manifest.js +46 -0
- package/dist/cjs/cloudflare-output-verifier/mutation-policy.js +7 -1
- package/dist/cjs/cloudflare-output-verifier/worker-bundles.js +10 -5
- package/dist/cjs/config.js +10 -5
- package/dist/cjs/deploy-output/npmAliases.js +92 -6
- package/dist/cjs/deploy-output/plugin.js +27 -6
- package/dist/cjs/deploy-output/public-assets.js +146 -0
- package/dist/cjs/deploy-output/target.js +57 -1
- package/dist/cjs/deploy-output/trace-files.js +99 -0
- package/dist/cjs/module-federation-config/exposes.js +70 -0
- package/dist/cjs/module-federation-config/index.js +73 -0
- package/dist/cjs/module-federation-config/inspect.js +103 -0
- package/dist/cjs/module-federation-config/object-literal.js +87 -0
- package/dist/cjs/module-federation-config/syntax.js +151 -0
- package/dist/cjs/module-federation-config/types.js +18 -0
- package/dist/cjs/native-type-checker.js +164 -18
- package/dist/cjs/policy-defaults.js +110 -0
- package/dist/cjs/release-envelope/canonical.js +2 -9
- package/dist/cjs/release-envelope/contract.js +68 -0
- package/dist/cjs/release-envelope/framework-output.js +296 -105
- package/dist/cjs/release-envelope/index.js +150 -36
- package/dist/cjs/release-envelope/plugin.js +24 -15
- package/dist/cjs/release-envelope/renderer-output-stamp.js +170 -0
- package/dist/cjs/release-envelope/types.js +10 -8
- package/dist/cjs/release-identity.js +8 -5
- package/dist/cjs/renderer-build-identity.js +134 -0
- package/dist/cjs/runtime-package-resolution.js +118 -0
- package/dist/cjs/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
- package/dist/cjs/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
- package/dist/cjs/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
- package/dist/cjs/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
- package/dist/cjs/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
- package/dist/cjs/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/cjs/workspace-topology.js +84 -0
- package/dist/esm/backend-federation-build/codegen.mjs +29 -18
- package/dist/esm/backend-federation-build/config.mjs +74 -38
- package/dist/esm/backend-federation-build/emit.mjs +64 -23
- package/dist/esm/build-config/build-environment.mjs +26 -160
- package/dist/esm/build-config/internal-effect-discovery.mjs +102 -0
- package/dist/esm/build-config/public.mjs +1 -1
- package/dist/esm/build-config/remote-address.mjs +20 -0
- package/dist/esm/cloudflare/artifacts.mjs +8 -19
- package/dist/esm/cloudflare/delivery-unit.mjs +124 -45
- package/dist/esm/cloudflare/index.mjs +15 -5
- package/dist/esm/cloudflare/native-renderer.mjs +48 -0
- package/dist/esm/cloudflare/renderer-identity.mjs +57 -0
- package/dist/esm/cloudflare/worker-manifest.mjs +55 -14
- package/dist/esm/cloudflare/worker-options.mjs +10 -0
- package/dist/esm/cloudflare/wrangler-config.mjs +203 -12
- package/dist/esm/cloudflare-builder.mjs +301 -89
- package/dist/esm/cloudflare-output-contract.mjs +29 -3
- package/dist/esm/cloudflare-output-verifier/identity.mjs +113 -24
- package/dist/esm/cloudflare-output-verifier/index.mjs +10 -0
- package/dist/esm/cloudflare-output-verifier/manifest.mjs +46 -0
- package/dist/esm/cloudflare-output-verifier/mutation-policy.mjs +7 -1
- package/dist/esm/cloudflare-output-verifier/worker-bundles.mjs +11 -6
- package/dist/esm/config.mjs +3 -1
- package/dist/esm/deploy-output/npmAliases.mjs +92 -6
- package/dist/esm/deploy-output/plugin.mjs +24 -6
- package/dist/esm/deploy-output/public-assets.mjs +82 -0
- package/dist/esm/deploy-output/target.mjs +50 -3
- package/dist/esm/deploy-output/trace-files.mjs +47 -0
- package/dist/esm/module-federation-config/exposes.mjs +18 -0
- package/dist/esm/module-federation-config/index.mjs +4 -0
- package/dist/esm/module-federation-config/inspect.mjs +65 -0
- package/dist/esm/module-federation-config/object-literal.mjs +43 -0
- package/dist/esm/module-federation-config/syntax.mjs +101 -0
- package/dist/esm/module-federation-config/types.mjs +0 -0
- package/dist/esm/native-type-checker.mjs +158 -18
- package/dist/esm/policy-defaults.mjs +51 -0
- package/dist/esm/release-envelope/canonical.mjs +2 -9
- package/dist/esm/release-envelope/contract.mjs +21 -0
- package/dist/esm/release-envelope/framework-output.mjs +297 -106
- package/dist/esm/release-envelope/index.mjs +149 -38
- package/dist/esm/release-envelope/plugin.mjs +24 -15
- package/dist/esm/release-envelope/renderer-output-stamp.mjs +112 -0
- package/dist/esm/release-envelope/types.mjs +2 -3
- package/dist/esm/release-identity.mjs +8 -5
- package/dist/esm/renderer-build-identity.mjs +82 -0
- package/dist/esm/runtime-package-resolution.mjs +60 -0
- package/dist/esm/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
- package/dist/esm/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
- package/dist/esm/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
- package/dist/esm/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
- package/dist/esm/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
- package/dist/esm/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/esm/workspace-topology.mjs +21 -0
- package/dist/esm-node/backend-federation-build/codegen.mjs +29 -18
- package/dist/esm-node/backend-federation-build/config.mjs +74 -38
- package/dist/esm-node/backend-federation-build/emit.mjs +64 -23
- package/dist/esm-node/build-config/build-environment.mjs +26 -160
- package/dist/esm-node/build-config/internal-effect-discovery.mjs +103 -0
- package/dist/esm-node/build-config/public.mjs +1 -1
- package/dist/esm-node/build-config/remote-address.mjs +21 -0
- package/dist/esm-node/cloudflare/artifacts.mjs +8 -19
- package/dist/esm-node/cloudflare/delivery-unit.mjs +124 -45
- package/dist/esm-node/cloudflare/index.mjs +15 -5
- package/dist/esm-node/cloudflare/native-renderer.mjs +49 -0
- package/dist/esm-node/cloudflare/renderer-identity.mjs +58 -0
- package/dist/esm-node/cloudflare/worker-manifest.mjs +55 -14
- package/dist/esm-node/cloudflare/worker-options.mjs +11 -0
- package/dist/esm-node/cloudflare/wrangler-config.mjs +203 -12
- package/dist/esm-node/cloudflare-builder.mjs +301 -89
- package/dist/esm-node/cloudflare-output-contract.mjs +29 -3
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +113 -24
- package/dist/esm-node/cloudflare-output-verifier/index.mjs +10 -0
- package/dist/esm-node/cloudflare-output-verifier/manifest.mjs +46 -0
- package/dist/esm-node/cloudflare-output-verifier/mutation-policy.mjs +7 -1
- package/dist/esm-node/cloudflare-output-verifier/worker-bundles.mjs +11 -6
- package/dist/esm-node/config.mjs +3 -1
- package/dist/esm-node/deploy-output/npmAliases.mjs +92 -6
- package/dist/esm-node/deploy-output/plugin.mjs +24 -6
- package/dist/esm-node/deploy-output/public-assets.mjs +83 -0
- package/dist/esm-node/deploy-output/target.mjs +50 -3
- package/dist/esm-node/deploy-output/trace-files.mjs +48 -0
- package/dist/esm-node/module-federation-config/exposes.mjs +19 -0
- package/dist/esm-node/module-federation-config/index.mjs +5 -0
- package/dist/esm-node/module-federation-config/inspect.mjs +66 -0
- package/dist/esm-node/module-federation-config/object-literal.mjs +44 -0
- package/dist/esm-node/module-federation-config/syntax.mjs +102 -0
- package/dist/esm-node/module-federation-config/types.mjs +1 -0
- package/dist/esm-node/native-type-checker.mjs +158 -18
- package/dist/esm-node/policy-defaults.mjs +52 -0
- package/dist/esm-node/release-envelope/canonical.mjs +2 -9
- package/dist/esm-node/release-envelope/contract.mjs +22 -0
- package/dist/esm-node/release-envelope/framework-output.mjs +297 -106
- package/dist/esm-node/release-envelope/index.mjs +149 -38
- package/dist/esm-node/release-envelope/plugin.mjs +24 -15
- package/dist/esm-node/release-envelope/renderer-output-stamp.mjs +113 -0
- package/dist/esm-node/release-envelope/types.mjs +2 -3
- package/dist/esm-node/release-identity.mjs +8 -5
- package/dist/esm-node/renderer-build-identity.mjs +83 -0
- package/dist/esm-node/runtime-package-resolution.mjs +61 -0
- package/dist/esm-node/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
- package/dist/esm-node/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
- package/dist/esm-node/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
- package/dist/esm-node/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
- package/dist/esm-node/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
- package/dist/esm-node/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/dist/esm-node/workspace-topology.mjs +22 -0
- package/dist/types/backend-federation-build/codegen.d.ts +30 -0
- package/dist/types/backend-federation-build/config.d.ts +10 -57
- package/dist/types/backend-federation-build/emit.d.ts +12 -2
- package/dist/types/backend-federation-build/index.d.ts +1 -1
- package/dist/types/build-config/build-environment.d.ts +1 -9
- package/dist/types/build-config/internal-effect-discovery.d.ts +4 -0
- package/dist/types/build-config/public.d.ts +1 -1
- package/dist/types/build-config/remote-address.d.ts +3 -0
- package/dist/types/cloudflare/artifacts.d.ts +7 -6
- package/dist/types/cloudflare/constants.d.ts +1 -1
- package/dist/types/cloudflare/delivery-unit.d.ts +38 -7
- package/dist/types/cloudflare/native-renderer.d.ts +30 -0
- package/dist/types/cloudflare/renderer-identity.d.ts +25 -0
- package/dist/types/cloudflare/worker-manifest.d.ts +17 -2
- package/dist/types/cloudflare/worker-options.d.ts +20 -0
- package/dist/types/cloudflare/wrangler-config.d.ts +55 -2
- package/dist/types/cloudflare-builder.d.ts +51 -8
- package/dist/types/cloudflare-output-contract.d.ts +5 -3
- package/dist/types/cloudflare-output-verifier/identity.d.ts +7 -3
- package/dist/types/cloudflare-output-verifier/worker-bundles.d.ts +2 -2
- package/dist/types/config.d.ts +98 -13
- package/dist/types/deploy-output/plugin.d.ts +29 -9
- package/dist/types/deploy-output/public-assets.d.ts +51 -0
- package/dist/types/deploy-output/target.d.ts +28 -5
- package/dist/types/deploy-output/trace-files.d.ts +17 -0
- package/dist/types/module-federation-config/exposes.d.ts +11 -0
- package/dist/types/module-federation-config/index.d.ts +5 -0
- package/dist/types/module-federation-config/inspect.d.ts +2 -0
- package/dist/types/module-federation-config/object-literal.d.ts +4 -0
- package/dist/types/module-federation-config/syntax.d.ts +7 -0
- package/dist/types/module-federation-config/types.d.ts +20 -0
- package/dist/types/native-type-checker.d.ts +26 -3
- package/dist/types/policy-defaults.d.ts +56 -0
- package/dist/types/release-envelope/canonical.d.ts +5 -5
- package/dist/types/release-envelope/contract.d.mts +8 -0
- package/dist/types/release-envelope/framework-output.d.ts +23 -13
- package/dist/types/release-envelope/index.d.ts +17 -5
- package/dist/types/release-envelope/plugin.d.ts +10 -6
- package/dist/types/release-envelope/renderer-output-stamp.d.ts +34 -0
- package/dist/types/release-envelope/types.d.ts +42 -4
- package/dist/types/release-identity.d.ts +3 -2
- package/dist/types/renderer-build-identity.d.ts +30 -0
- package/dist/types/runtime-package-resolution.d.ts +28 -0
- package/dist/types/templates/cloudflare-worker-route-data.d.mts +14 -0
- package/dist/types/workspace-topology.d.ts +91 -0
- package/package.json +97 -26
- package/src/backend-federation-build/codegen.ts +28 -19
- package/src/backend-federation-build/config.ts +189 -123
- package/src/backend-federation-build/emit.ts +119 -38
- package/src/backend-federation-build/index.ts +4 -1
- package/src/build-config/build-environment.ts +59 -285
- package/src/build-config/internal-effect-discovery.ts +239 -0
- package/src/build-config/public.ts +0 -1
- package/src/build-config/remote-address.ts +33 -0
- package/src/cloudflare/artifacts.ts +22 -43
- package/src/cloudflare/delivery-unit.ts +283 -60
- package/src/cloudflare/index.ts +35 -19
- package/src/cloudflare/native-renderer.ts +117 -0
- package/src/cloudflare/renderer-identity.ts +171 -0
- package/src/cloudflare/worker-manifest.ts +108 -10
- package/src/cloudflare/worker-options.ts +60 -0
- package/src/cloudflare/wrangler-config.ts +390 -14
- package/src/cloudflare-builder.ts +543 -170
- package/src/cloudflare-output-contract.ts +45 -2
- package/src/cloudflare-output-verifier/identity.ts +138 -7
- package/src/cloudflare-output-verifier/index.ts +20 -0
- package/src/cloudflare-output-verifier/manifest.ts +77 -0
- package/src/cloudflare-output-verifier/mutation-policy.ts +11 -1
- package/src/cloudflare-output-verifier/worker-bundles.ts +27 -11
- package/src/config.ts +101 -19
- package/src/deploy-output/npmAliases.ts +141 -4
- package/src/deploy-output/plugin.ts +55 -18
- package/src/deploy-output/public-assets.ts +224 -0
- package/src/deploy-output/target.ts +97 -14
- package/src/deploy-output/trace-files.ts +80 -0
- package/src/module-federation-config/exposes.ts +46 -0
- package/src/module-federation-config/index.ts +22 -0
- package/src/module-federation-config/inspect.ts +157 -0
- package/src/module-federation-config/object-literal.ts +76 -0
- package/src/module-federation-config/syntax.ts +212 -0
- package/src/module-federation-config/types.ts +22 -0
- package/src/native-type-checker.ts +281 -26
- package/src/policy-defaults.ts +157 -0
- package/src/release-envelope/canonical.ts +6 -18
- package/src/release-envelope/contract.mjs +28 -0
- package/src/release-envelope/framework-output.ts +681 -156
- package/src/release-envelope/index.ts +315 -60
- package/src/release-envelope/plugin.ts +54 -38
- package/src/release-envelope/renderer-output-stamp.ts +210 -0
- package/src/release-envelope/types.ts +75 -6
- package/src/release-identity.ts +19 -2
- package/src/renderer-build-identity.ts +168 -0
- package/src/runtime-package-resolution.ts +115 -0
- package/src/templates/cloudflare-entry.001-bootstrap-security.mjs +14 -6
- package/src/templates/cloudflare-entry.002-assets-routes.mjs +10 -2
- package/src/templates/cloudflare-entry.004-rendering-css.mjs +403 -96
- package/src/templates/cloudflare-entry.005-worker-dispatch.mjs +530 -103
- package/src/templates/cloudflare-entry.006-fetch-handler.mjs +26 -7
- package/src/templates/cloudflare-worker-route-data.mjs +1069 -0
- package/src/workspace-topology.ts +125 -0
- package/dist/cjs/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/dist/cjs/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/dist/cjs/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/dist/cjs/templates/cloudflare-worker-path.mjs +0 -98
- package/dist/esm/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/dist/esm/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/dist/esm/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/dist/esm/templates/cloudflare-worker-path.mjs +0 -98
- package/dist/esm-node/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/dist/esm-node/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/dist/esm-node/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- package/dist/esm-node/templates/cloudflare-worker-path.mjs +0 -98
- package/dist/types/templates/cloudflare-worker-fs-promises.d.mts +0 -5
- package/dist/types/templates/cloudflare-worker-loadable-server.d.mts +0 -48
- package/dist/types/templates/cloudflare-worker-mf-ssr-runtime-plugin.d.mts +0 -13
- package/dist/types/templates/cloudflare-worker-path.d.mts +0 -21
- package/src/templates/cloudflare-worker-fs-promises.mjs +0 -11
- package/src/templates/cloudflare-worker-loadable-server.mjs +0 -281
- package/src/templates/cloudflare-worker-mf-ssr-runtime-plugin.mjs +0 -14
- 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
|
-
|
|
1
|
+
import type {
|
|
2
|
+
RendererIdentity,
|
|
3
|
+
RendererProfile,
|
|
4
|
+
RendererRouterBindings,
|
|
5
|
+
} from '@modern-js/backend-federation-contracts';
|
|
2
6
|
|
|
3
|
-
|
|
4
|
-
|
|
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
|
-
|
|
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
|
};
|
package/src/release-identity.ts
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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
|
+
};
|