@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.9 → 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 +130 -13
- package/dist/cjs/policy-defaults.js +38 -29
- 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/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 +124 -13
- package/dist/esm/policy-defaults.mjs +36 -30
- 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/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 +124 -13
- package/dist/esm-node/policy-defaults.mjs +36 -30
- 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/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 +12 -2
- 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/templates/cloudflare-worker-route-data.d.mts +14 -0
- package/dist/types/workspace-topology.d.ts +91 -0
- package/package.json +87 -27
- 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 +233 -20
- package/src/policy-defaults.ts +47 -17
- 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/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
|
@@ -1,25 +1,37 @@
|
|
|
1
1
|
import { createHash } from 'node:crypto';
|
|
2
2
|
import fs from 'node:fs/promises';
|
|
3
3
|
import path from 'node:path';
|
|
4
|
+
import { isDeepStrictEqual } from 'node:util';
|
|
4
5
|
import {
|
|
6
|
+
assertRendererProfileCompatibility,
|
|
5
7
|
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
6
8
|
BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
7
9
|
isUltramodernBuildArtifact,
|
|
10
|
+
type RendererProfile,
|
|
8
11
|
ULTRAMODERN_BUILD_ARTIFACT_FILE,
|
|
12
|
+
ULTRAMODERN_BUILD_ARTIFACT_PATH,
|
|
9
13
|
type UltramodernBuildArtifact,
|
|
10
14
|
} from '@modern-js/backend-federation-contracts';
|
|
15
|
+
import { readBuildIdentity } from '../backend-federation-build/config';
|
|
16
|
+
import {
|
|
17
|
+
resolveTopologyDeliveryUnit,
|
|
18
|
+
resolveTopologyReleaseRole,
|
|
19
|
+
resolveWorkerDeliveryUnitStamp,
|
|
20
|
+
} from '../cloudflare/delivery-unit';
|
|
11
21
|
import {
|
|
12
22
|
canonicalSerializeMicroVerticalReleaseEnvelope,
|
|
13
23
|
createMicroVerticalReleaseEnvelope,
|
|
14
24
|
verifyMicroVerticalReleaseEnvelope,
|
|
15
25
|
} from './index';
|
|
16
|
-
import
|
|
17
|
-
MicroVerticalReleaseArtifact,
|
|
18
|
-
MicroVerticalReleaseArtifactInput,
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
26
|
+
import {
|
|
27
|
+
type MicroVerticalReleaseArtifact,
|
|
28
|
+
type MicroVerticalReleaseArtifactInput,
|
|
29
|
+
type MicroVerticalReleaseIdentity,
|
|
30
|
+
type MicroVerticalReleaseSurfaces,
|
|
31
|
+
type MicroVerticalReleaseTarget,
|
|
32
|
+
type MicroVerticalReleaseUi,
|
|
33
|
+
type ReleaseEnvelope,
|
|
34
|
+
SHELL_RELEASE_ENVELOPE_KIND,
|
|
23
35
|
} from './types';
|
|
24
36
|
|
|
25
37
|
export const MICROVERTICAL_RELEASE_ENVELOPE_PATH =
|
|
@@ -33,6 +45,42 @@ const MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_SCHEMA_VERSION = 1;
|
|
|
33
45
|
|
|
34
46
|
const COMPILED_MODULE_PATTERN = /\.(?:c|m)?js$/u;
|
|
35
47
|
const EFFECT_BFF_WORKER_PATTERN = /^worker\/__modern_bff_effect\.(?:c|m)?js$/u;
|
|
48
|
+
const EFFECT_BFF_WORKER_SUPPORT_PATTERN =
|
|
49
|
+
/^worker\/__modern_worker_(?:runtime|shared)_*\.(?:c|m)?js$/u;
|
|
50
|
+
const isEffectBffWorkerArtifact = (logicalPath: string) =>
|
|
51
|
+
EFFECT_BFF_WORKER_PATTERN.test(logicalPath) ||
|
|
52
|
+
EFFECT_BFF_WORKER_SUPPORT_PATTERN.test(logicalPath);
|
|
53
|
+
const effectBffWorkerArtifacts = (files: string[]) =>
|
|
54
|
+
files.some(logicalPath => EFFECT_BFF_WORKER_PATTERN.test(logicalPath))
|
|
55
|
+
? files.filter(isEffectBffWorkerArtifact)
|
|
56
|
+
: [];
|
|
57
|
+
const CRAWLER_POLICY_PATH = 'public/robots.txt';
|
|
58
|
+
const isPublicMetadataPath = (logicalPath: string) =>
|
|
59
|
+
logicalPath === 'public/_headers' ||
|
|
60
|
+
/^public\/\.well-known\/[^/]+\.json$/u.test(logicalPath);
|
|
61
|
+
const PUBLIC_BUILD_ARTIFACT_PATH = `public/${ULTRAMODERN_BUILD_ARTIFACT_FILE}`;
|
|
62
|
+
// Files an app declared through `deploy.<target>.publicAssets`. The deploy
|
|
63
|
+
// output stages them from app-root sources; they are served data, not a
|
|
64
|
+
// UI/client surface, so API-only units may carry them.
|
|
65
|
+
const DECLARED_PUBLIC_ASSET_RUNTIME = 'public-asset';
|
|
66
|
+
|
|
67
|
+
const resolveDeclaredPublicAssets = (
|
|
68
|
+
files: readonly string[],
|
|
69
|
+
declaredPublicAssets: readonly string[],
|
|
70
|
+
target: 'Node' | 'Cloudflare',
|
|
71
|
+
) => {
|
|
72
|
+
const staged = new Set(files);
|
|
73
|
+
const declared = new Set<string>();
|
|
74
|
+
for (const logicalPath of declaredPublicAssets) {
|
|
75
|
+
if (!logicalPath.startsWith('public/') || !staged.has(logicalPath)) {
|
|
76
|
+
throw new Error(
|
|
77
|
+
`[ultramodern-release-envelope] final ${target} staging has no declared public asset file ${JSON.stringify(logicalPath)}.`,
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
declared.add(logicalPath);
|
|
81
|
+
}
|
|
82
|
+
return declared;
|
|
83
|
+
};
|
|
36
84
|
|
|
37
85
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
38
86
|
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -57,8 +105,8 @@ const collectNestedStrings = (value: unknown, key: string): string[] => {
|
|
|
57
105
|
]);
|
|
58
106
|
};
|
|
59
107
|
|
|
60
|
-
//
|
|
61
|
-
// Only
|
|
108
|
+
// Native MF consumers can import remotes without emitting their own remote entry.
|
|
109
|
+
// Only local routeAssets declarations may supply their browser evidence.
|
|
62
110
|
const emptyProducerRouteModules = async (
|
|
63
111
|
distDirectory: string,
|
|
64
112
|
files: string[],
|
|
@@ -70,7 +118,14 @@ const emptyProducerRouteModules = async (
|
|
|
70
118
|
!Array.isArray(manifest.exposes) ||
|
|
71
119
|
manifest.exposes.length !== 0 ||
|
|
72
120
|
!Array.isArray(manifest.remotes) ||
|
|
73
|
-
manifest.remotes.
|
|
121
|
+
manifest.remotes.some(
|
|
122
|
+
remote =>
|
|
123
|
+
!isRecord(remote) ||
|
|
124
|
+
['federationContainerName', 'moduleName', 'alias', 'entry'].some(
|
|
125
|
+
key =>
|
|
126
|
+
typeof remote[key] !== 'string' || remote[key].trim().length === 0,
|
|
127
|
+
),
|
|
128
|
+
) ||
|
|
74
129
|
!isRecord(manifest.metaData) ||
|
|
75
130
|
!isRecord(manifest.metaData.remoteEntry) ||
|
|
76
131
|
manifest.metaData.remoteEntry.name !== '' ||
|
|
@@ -303,7 +358,7 @@ const resolveSafeReleaseEnvelopePath = async ({
|
|
|
303
358
|
|
|
304
359
|
const writeReleaseEnvelope = async (
|
|
305
360
|
artifactRoot: string,
|
|
306
|
-
envelope:
|
|
361
|
+
envelope: ReleaseEnvelope,
|
|
307
362
|
) => {
|
|
308
363
|
const envelopePath = await resolveSafeReleaseEnvelopePath({
|
|
309
364
|
artifactRoot,
|
|
@@ -322,7 +377,8 @@ const writeReleaseEnvelope = async (
|
|
|
322
377
|
|
|
323
378
|
const readBuildArtifact = async (
|
|
324
379
|
distDirectory: string,
|
|
325
|
-
|
|
380
|
+
requirePromotable = true,
|
|
381
|
+
): Promise<UltramodernBuildArtifact | undefined> => {
|
|
326
382
|
const artifactPath = path.join(
|
|
327
383
|
distDirectory,
|
|
328
384
|
ULTRAMODERN_BUILD_ARTIFACT_FILE,
|
|
@@ -334,8 +390,17 @@ const readBuildArtifact = async (
|
|
|
334
390
|
);
|
|
335
391
|
}
|
|
336
392
|
if (artifact.deliveryUnit.sourceRevision === 'workspace') {
|
|
393
|
+
// `resolveUltramodernSourceRevision` deliberately labels a dirty or
|
|
394
|
+
// non-Git checkout `workspace` so development stays usable; only a
|
|
395
|
+
// promotion may not carry that identity. A plain `modern build` is the
|
|
396
|
+
// development path, so it skips the envelope instead of failing the
|
|
397
|
+
// build. The deploy path still asks for a promotable artifact, which is
|
|
398
|
+
// where the release gate belongs.
|
|
399
|
+
if (!requirePromotable) {
|
|
400
|
+
return undefined;
|
|
401
|
+
}
|
|
337
402
|
throw new Error(
|
|
338
|
-
'[ultramodern-release-envelope] sourceRevision "workspace" cannot produce a promotable
|
|
403
|
+
'[ultramodern-release-envelope] sourceRevision "workspace" cannot produce a promotable envelope.',
|
|
339
404
|
);
|
|
340
405
|
}
|
|
341
406
|
return artifact;
|
|
@@ -491,9 +556,8 @@ type ReleaseIdentityCarrierSurface =
|
|
|
491
556
|
| 'ssr'
|
|
492
557
|
| 'uiClient';
|
|
493
558
|
|
|
494
|
-
type ReleaseIdentityCarrierPaths =
|
|
495
|
-
ReleaseIdentityCarrierSurface,
|
|
496
|
-
string[]
|
|
559
|
+
type ReleaseIdentityCarrierPaths = Partial<
|
|
560
|
+
Record<ReleaseIdentityCarrierSurface, string[]>
|
|
497
561
|
>;
|
|
498
562
|
|
|
499
563
|
const writeReleaseIdentityCarrierMetadata = async (
|
|
@@ -506,7 +570,11 @@ const writeReleaseIdentityCarrierMetadata = async (
|
|
|
506
570
|
ReleaseIdentityCarrierSurface,
|
|
507
571
|
string[],
|
|
508
572
|
][]) {
|
|
509
|
-
if (
|
|
573
|
+
if (
|
|
574
|
+
logicalPaths.length === 0 &&
|
|
575
|
+
surface !== 'ssr' &&
|
|
576
|
+
surface !== 'uiClient'
|
|
577
|
+
) {
|
|
510
578
|
throw new Error(
|
|
511
579
|
`[ultramodern-release-envelope] ${surface} has no declared release-identity carrier artifact.`,
|
|
512
580
|
);
|
|
@@ -581,12 +649,18 @@ const createReleaseArtifactInputs = async (
|
|
|
581
649
|
distDirectory: string,
|
|
582
650
|
identity: MicroVerticalReleaseIdentity,
|
|
583
651
|
target: MicroVerticalReleaseTarget,
|
|
652
|
+
apiOnly: boolean,
|
|
653
|
+
uiOnly: boolean,
|
|
654
|
+
shellConsumer: boolean,
|
|
584
655
|
) => {
|
|
656
|
+
// A Shell consumes remotes and a UI-only unit has no backend; neither
|
|
657
|
+
// publishes a backend federation pair.
|
|
658
|
+
const backendProducer = !uiOnly && !shellConsumer;
|
|
585
659
|
const files = await collectFiles(distDirectory);
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
) {
|
|
660
|
+
// Rspack emits these runtime/shared chunks for UI SSR too. Only an actual
|
|
661
|
+
// Effect BFF entry makes the shared worker support part of that API surface.
|
|
662
|
+
const effectWorkerPaths = effectBffWorkerArtifacts(files);
|
|
663
|
+
if (target === 'node' && files.some(isEffectBffWorkerArtifact)) {
|
|
590
664
|
throw new Error(
|
|
591
665
|
'[ultramodern-release-envelope] Node target conflicts with a Cloudflare Effect API/BFF worker artifact; resolve the canonical deploy target instead of emitting a Node envelope for Cloudflare output.',
|
|
592
666
|
);
|
|
@@ -596,7 +670,10 @@ const createReleaseArtifactInputs = async (
|
|
|
596
670
|
return (
|
|
597
671
|
topLevel === 'static' ||
|
|
598
672
|
topLevel === 'html' ||
|
|
599
|
-
topLevel === 'public'
|
|
673
|
+
(topLevel === 'public' &&
|
|
674
|
+
logicalPath !== CRAWLER_POLICY_PATH &&
|
|
675
|
+
logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH &&
|
|
676
|
+
!isPublicMetadataPath(logicalPath)) ||
|
|
600
677
|
[
|
|
601
678
|
'index.html',
|
|
602
679
|
'mf-manifest.json',
|
|
@@ -618,7 +695,7 @@ const createReleaseArtifactInputs = async (
|
|
|
618
695
|
logicalPath =>
|
|
619
696
|
logicalPath.startsWith('worker/') &&
|
|
620
697
|
COMPILED_MODULE_PATTERN.test(logicalPath) &&
|
|
621
|
-
!
|
|
698
|
+
!effectWorkerPaths.includes(logicalPath),
|
|
622
699
|
);
|
|
623
700
|
const nodeApiEntryPaths = files.filter(
|
|
624
701
|
logicalPath =>
|
|
@@ -633,44 +710,59 @@ const createReleaseArtifactInputs = async (
|
|
|
633
710
|
logicalPath.startsWith('shared/')) &&
|
|
634
711
|
COMPILED_MODULE_PATTERN.test(logicalPath),
|
|
635
712
|
)
|
|
636
|
-
:
|
|
637
|
-
|
|
638
|
-
|
|
713
|
+
: effectWorkerPaths;
|
|
714
|
+
|
|
715
|
+
if (
|
|
716
|
+
backendProducer &&
|
|
717
|
+
target === 'cloudflare' &&
|
|
718
|
+
effectWorkerPaths.length === 0
|
|
719
|
+
) {
|
|
720
|
+
throw new Error(
|
|
721
|
+
'[ultramodern-release-envelope] cloudflare MicroVertical has no actual Effect API/BFF worker artifact.',
|
|
722
|
+
);
|
|
723
|
+
}
|
|
639
724
|
|
|
640
|
-
if (uiClientPaths.length
|
|
725
|
+
if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.length > 0)) {
|
|
726
|
+
throw new Error(
|
|
727
|
+
'[ultramodern-release-envelope] API-only MicroVertical emitted an undeclared UI/client or SSR surface.',
|
|
728
|
+
);
|
|
729
|
+
}
|
|
730
|
+
if (!apiOnly && uiClientPaths.length === 0) {
|
|
641
731
|
throw new Error(
|
|
642
732
|
`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no UI/client artifacts.`,
|
|
643
733
|
);
|
|
644
734
|
}
|
|
645
|
-
if (ssrPaths.length === 0) {
|
|
735
|
+
if (!apiOnly && ssrPaths.length === 0) {
|
|
646
736
|
throw new Error(
|
|
647
737
|
`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no ${target === 'node' ? 'Node' : 'workerd'} SSR artifacts.`,
|
|
648
738
|
);
|
|
649
739
|
}
|
|
740
|
+
if (uiOnly && (apiBackendPaths.length > 0 || nodeApiEntryPaths.length > 0))
|
|
741
|
+
throw new Error(
|
|
742
|
+
'[ultramodern-release-envelope] UI-only application emitted an undeclared API/backend artifact.',
|
|
743
|
+
);
|
|
650
744
|
if (
|
|
651
|
-
|
|
652
|
-
|
|
745
|
+
backendProducer &&
|
|
746
|
+
((target === 'node' && nodeApiEntryPaths.length === 0) ||
|
|
747
|
+
apiBackendPaths.length === 0)
|
|
653
748
|
) {
|
|
654
749
|
throw new Error(
|
|
655
|
-
`[ultramodern-release-envelope] ${target}
|
|
750
|
+
`[ultramodern-release-envelope] ${target} MicroVertical has no actual ${target === 'node' ? 'compiled Node Effect API artifact' : 'Effect API/BFF worker artifact'}.`,
|
|
656
751
|
);
|
|
657
752
|
}
|
|
658
753
|
|
|
659
|
-
const clientExecutionPaths =
|
|
660
|
-
|
|
661
|
-
files
|
|
662
|
-
)
|
|
663
|
-
if (clientExecutionPaths.length === 0) {
|
|
754
|
+
const clientExecutionPaths = apiOnly
|
|
755
|
+
? []
|
|
756
|
+
: await manifestReferencedClientModules(distDirectory, files);
|
|
757
|
+
if (!apiOnly && clientExecutionPaths.length === 0) {
|
|
664
758
|
throw new Error(
|
|
665
759
|
'[ultramodern-release-envelope] UI/client manifest references no compiled execution module.',
|
|
666
760
|
);
|
|
667
761
|
}
|
|
668
|
-
const ssrExecutionPaths =
|
|
669
|
-
|
|
670
|
-
files,
|
|
671
|
-
|
|
672
|
-
);
|
|
673
|
-
if (ssrExecutionPaths.length === 0) {
|
|
762
|
+
const ssrExecutionPaths = apiOnly
|
|
763
|
+
? []
|
|
764
|
+
: await routeReferencedSsrModules(distDirectory, files, target);
|
|
765
|
+
if (!apiOnly && ssrExecutionPaths.length === 0) {
|
|
674
766
|
throw new Error(
|
|
675
767
|
`[ultramodern-release-envelope] route manifest references no emitted ${target === 'node' ? 'Node' : 'Cloudflare'} SSR execution module.`,
|
|
676
768
|
);
|
|
@@ -689,13 +781,34 @@ const createReleaseArtifactInputs = async (
|
|
|
689
781
|
}
|
|
690
782
|
};
|
|
691
783
|
add(uiClientPaths, 'browser');
|
|
784
|
+
add(
|
|
785
|
+
files.filter(logicalPath => logicalPath === CRAWLER_POLICY_PATH),
|
|
786
|
+
'crawler-policy',
|
|
787
|
+
);
|
|
788
|
+
add(files.filter(isPublicMetadataPath), 'public-metadata');
|
|
692
789
|
add(ssrPaths, target === 'node' ? 'nodejs' : 'workerd');
|
|
693
790
|
add(apiBackendPaths, target === 'node' ? 'nodejs' : 'workerd-effect');
|
|
694
|
-
add([
|
|
791
|
+
add([ULTRAMODERN_BUILD_ARTIFACT_FILE], 'release-identity-metadata');
|
|
695
792
|
add(
|
|
696
|
-
|
|
697
|
-
|
|
793
|
+
files.filter(logicalPath => logicalPath === PUBLIC_BUILD_ARTIFACT_PATH),
|
|
794
|
+
'release-identity-metadata',
|
|
698
795
|
);
|
|
796
|
+
if (backendProducer) {
|
|
797
|
+
add([BACKEND_FEDERATION_MANIFEST_FILE], 'module-federation-manifest');
|
|
798
|
+
add(
|
|
799
|
+
[BACKEND_FEDERATION_REMOTE_ENTRY_FILE],
|
|
800
|
+
target === 'node' ? 'nodejs' : 'commonjs-module',
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
if (shellConsumer) {
|
|
804
|
+
// The Shell is the deployed entry point: every remaining build file is
|
|
805
|
+
// part of its deployment and must be bound.
|
|
806
|
+
for (const logicalPath of files) {
|
|
807
|
+
if (!runtimeByPath.has(logicalPath)) {
|
|
808
|
+
runtimeByPath.set(logicalPath, `${target}-deployment`);
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
}
|
|
699
812
|
|
|
700
813
|
const surfaces: MicroVerticalReleaseSurfaces = {
|
|
701
814
|
uiClient: [...uiClientPaths].sort((left, right) =>
|
|
@@ -705,10 +818,14 @@ const createReleaseArtifactInputs = async (
|
|
|
705
818
|
apiBackend: [...apiBackendPaths].sort((left, right) =>
|
|
706
819
|
left.localeCompare(right),
|
|
707
820
|
),
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
821
|
+
...(backendProducer
|
|
822
|
+
? {
|
|
823
|
+
backendFederation: {
|
|
824
|
+
manifest: BACKEND_FEDERATION_MANIFEST_FILE,
|
|
825
|
+
container: BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
826
|
+
},
|
|
827
|
+
}
|
|
828
|
+
: {}),
|
|
712
829
|
};
|
|
713
830
|
const artifacts = [...runtimeByPath]
|
|
714
831
|
.map(([logicalPath, runtime]) => releaseArtifact(logicalPath, runtime))
|
|
@@ -717,11 +834,15 @@ const createReleaseArtifactInputs = async (
|
|
|
717
834
|
distDirectory,
|
|
718
835
|
identity,
|
|
719
836
|
{
|
|
720
|
-
apiBackend: apiBackendPaths,
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
837
|
+
...(apiBackendPaths.length > 0 ? { apiBackend: apiBackendPaths } : {}),
|
|
838
|
+
...(backendProducer
|
|
839
|
+
? {
|
|
840
|
+
backendFederation: [
|
|
841
|
+
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
842
|
+
BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
843
|
+
],
|
|
844
|
+
}
|
|
845
|
+
: {}),
|
|
725
846
|
ssr: [...new Set([...ssrExecutionPaths, ...ssrPaths])],
|
|
726
847
|
uiClient: uiClientPaths.filter(logicalPath =>
|
|
727
848
|
COMPILED_MODULE_PATTERN.test(logicalPath),
|
|
@@ -736,6 +857,43 @@ const createReleaseArtifactInputs = async (
|
|
|
736
857
|
};
|
|
737
858
|
};
|
|
738
859
|
|
|
860
|
+
const releaseEnvelopeSurfaces = (
|
|
861
|
+
surfaces: MicroVerticalReleaseSurfaces,
|
|
862
|
+
shellConsumer: boolean,
|
|
863
|
+
) => {
|
|
864
|
+
if (!shellConsumer) {
|
|
865
|
+
return { surfaces };
|
|
866
|
+
}
|
|
867
|
+
const { uiClient, ssr, apiBackend } = surfaces;
|
|
868
|
+
return {
|
|
869
|
+
kind: SHELL_RELEASE_ENVELOPE_KIND,
|
|
870
|
+
surfaces: { uiClient, ssr, apiBackend },
|
|
871
|
+
};
|
|
872
|
+
};
|
|
873
|
+
|
|
874
|
+
/**
|
|
875
|
+
* A Shell publishes no backend federation pair, so no producer restamps its
|
|
876
|
+
* identity. Its finalized build artifact (stamped by the renderer build from the
|
|
877
|
+
* declared delivery unit) must belong to this app; the build-artifact contract
|
|
878
|
+
* requires every identity record to carry that one stamped identity.
|
|
879
|
+
*/
|
|
880
|
+
const assertShellBuildIdentity = async (
|
|
881
|
+
appDirectory: string,
|
|
882
|
+
distDirectory: string,
|
|
883
|
+
) => {
|
|
884
|
+
const source = await readBuildIdentity(appDirectory);
|
|
885
|
+
const finalized = (await pathExists(
|
|
886
|
+
path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE),
|
|
887
|
+
))
|
|
888
|
+
? await resolveWorkerDeliveryUnitStamp(appDirectory, distDirectory)
|
|
889
|
+
: undefined;
|
|
890
|
+
if (!source.artifact || !finalized || finalized.unitId !== source.unitId) {
|
|
891
|
+
throw new Error(
|
|
892
|
+
'[ultramodern-release-envelope] Shell is missing its native compiled delivery-unit identity.',
|
|
893
|
+
);
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
|
|
739
897
|
const pathExists = async (filePath: string) => {
|
|
740
898
|
try {
|
|
741
899
|
await fs.access(filePath);
|
|
@@ -747,16 +905,34 @@ const pathExists = async (filePath: string) => {
|
|
|
747
905
|
|
|
748
906
|
export const emitFrameworkMicroVerticalReleaseEnvelope = async ({
|
|
749
907
|
apiOnly,
|
|
908
|
+
appDirectory,
|
|
750
909
|
distDirectory,
|
|
910
|
+
requirePromotable = true,
|
|
911
|
+
expectedRendererProfile,
|
|
912
|
+
role,
|
|
751
913
|
target,
|
|
752
914
|
}: {
|
|
753
915
|
apiOnly: boolean;
|
|
916
|
+
appDirectory?: string;
|
|
754
917
|
distDirectory: string;
|
|
918
|
+
requirePromotable?: boolean;
|
|
919
|
+
expectedRendererProfile?: RendererProfile;
|
|
920
|
+
role?: 'microvertical' | 'shell';
|
|
755
921
|
target: MicroVerticalReleaseTarget;
|
|
756
|
-
}): Promise<
|
|
757
|
-
|
|
758
|
-
|
|
922
|
+
}): Promise<ReleaseEnvelope | undefined> => {
|
|
923
|
+
const declaredRole = appDirectory
|
|
924
|
+
? await resolveTopologyReleaseRole(appDirectory)
|
|
925
|
+
: undefined;
|
|
926
|
+
if (
|
|
927
|
+
role !== undefined &&
|
|
928
|
+
declaredRole !== undefined &&
|
|
929
|
+
role !== declaredRole
|
|
930
|
+
) {
|
|
931
|
+
throw new Error(
|
|
932
|
+
'[ultramodern-release-envelope] Configured release role disagrees with the declared topology app.',
|
|
933
|
+
);
|
|
759
934
|
}
|
|
935
|
+
const shellConsumer = (declaredRole ?? role ?? 'microvertical') === 'shell';
|
|
760
936
|
const backendManifestPath = path.join(
|
|
761
937
|
distDirectory,
|
|
762
938
|
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
@@ -769,35 +945,115 @@ export const emitFrameworkMicroVerticalReleaseEnvelope = async ({
|
|
|
769
945
|
pathExists(backendManifestPath),
|
|
770
946
|
pathExists(backendContainerPath),
|
|
771
947
|
]);
|
|
772
|
-
if (
|
|
773
|
-
|
|
948
|
+
if (shellConsumer) {
|
|
949
|
+
if (apiOnly || hasBackendManifest || hasBackendContainer || !appDirectory) {
|
|
950
|
+
throw new Error(
|
|
951
|
+
'[ultramodern-release-envelope] Shell requires a full-stack application source and cannot declare a backend federation producer.',
|
|
952
|
+
);
|
|
953
|
+
}
|
|
954
|
+
await assertShellBuildIdentity(appDirectory, distDirectory);
|
|
955
|
+
} else if (!apiOnly && !hasBackendManifest && !hasBackendContainer) {
|
|
956
|
+
const source = appDirectory
|
|
957
|
+
? path.join(appDirectory, ULTRAMODERN_BUILD_ARTIFACT_PATH)
|
|
958
|
+
: undefined;
|
|
959
|
+
const participationPaths = [
|
|
960
|
+
path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE),
|
|
961
|
+
backendManifestPath,
|
|
962
|
+
backendContainerPath,
|
|
963
|
+
...(source ? [source, source.replace(/\.json$/u, '.ts')] : []),
|
|
964
|
+
];
|
|
965
|
+
const hasParticipationEntry = (
|
|
966
|
+
await Promise.all(
|
|
967
|
+
participationPaths.map(async filePath => {
|
|
968
|
+
try {
|
|
969
|
+
await fs.lstat(filePath);
|
|
970
|
+
return true;
|
|
971
|
+
} catch (error) {
|
|
972
|
+
if (isMissingPathError(error)) return false;
|
|
973
|
+
throw error;
|
|
974
|
+
}
|
|
975
|
+
}),
|
|
976
|
+
)
|
|
977
|
+
).some(Boolean);
|
|
978
|
+
if (
|
|
979
|
+
!hasParticipationEntry &&
|
|
980
|
+
(!appDirectory || !(await resolveTopologyDeliveryUnit(appDirectory)))
|
|
981
|
+
)
|
|
982
|
+
return undefined;
|
|
774
983
|
}
|
|
775
|
-
|
|
984
|
+
const buildArtifact = await readBuildArtifact(
|
|
985
|
+
distDirectory,
|
|
986
|
+
requirePromotable,
|
|
987
|
+
);
|
|
988
|
+
const declared =
|
|
989
|
+
appDirectory && buildArtifact
|
|
990
|
+
? await resolveWorkerDeliveryUnitStamp(appDirectory, distDirectory)
|
|
991
|
+
: undefined;
|
|
992
|
+
const uiOnly = Boolean(declared?.surfaces.ui && !declared.surfaces.api);
|
|
993
|
+
if (uiOnly && (apiOnly || hasBackendManifest || hasBackendContainer))
|
|
776
994
|
throw new Error(
|
|
777
|
-
'[ultramodern-release-envelope]
|
|
995
|
+
'[ultramodern-release-envelope] UI-only application cannot declare API-only configuration or backend federation output.',
|
|
996
|
+
);
|
|
997
|
+
if (!hasBackendManifest && !hasBackendContainer && !apiOnly && !buildArtifact)
|
|
998
|
+
return undefined;
|
|
999
|
+
if (
|
|
1000
|
+
!shellConsumer &&
|
|
1001
|
+
!uiOnly &&
|
|
1002
|
+
(!hasBackendManifest || !hasBackendContainer)
|
|
1003
|
+
) {
|
|
1004
|
+
throw new Error(
|
|
1005
|
+
'[ultramodern-release-envelope] MicroVertical backend federation manifest and container must be emitted together.',
|
|
778
1006
|
);
|
|
779
1007
|
}
|
|
780
1008
|
|
|
781
|
-
|
|
1009
|
+
if (!buildArtifact) {
|
|
1010
|
+
return undefined;
|
|
1011
|
+
}
|
|
1012
|
+
const builtUi = buildArtifact.surfaces.ui;
|
|
1013
|
+
if (Boolean(builtUi) === apiOnly) {
|
|
1014
|
+
throw new Error(
|
|
1015
|
+
'[ultramodern-release-envelope] build artifact UI surface must match the configured application surface.',
|
|
1016
|
+
);
|
|
1017
|
+
}
|
|
1018
|
+
if (expectedRendererProfile !== undefined) {
|
|
1019
|
+
assertRendererProfileCompatibility(
|
|
1020
|
+
expectedRendererProfile,
|
|
1021
|
+
builtUi?.rendererProfile,
|
|
1022
|
+
);
|
|
1023
|
+
}
|
|
1024
|
+
const ui: MicroVerticalReleaseUi | undefined = builtUi
|
|
1025
|
+
? {
|
|
1026
|
+
rendererIdentity: builtUi.rendererIdentity,
|
|
1027
|
+
rendererProfile: builtUi.rendererProfile,
|
|
1028
|
+
routerBindings: builtUi.routerBindings,
|
|
1029
|
+
}
|
|
1030
|
+
: undefined;
|
|
782
1031
|
const identity = {
|
|
783
1032
|
unitId: buildArtifact.deliveryUnit.unitId,
|
|
784
1033
|
buildMarker: buildArtifact.deliveryUnit.buildMarker,
|
|
785
1034
|
sourceRevision: buildArtifact.deliveryUnit.sourceRevision,
|
|
786
1035
|
releaseVersion: buildArtifact.deliveryUnit.version,
|
|
787
1036
|
};
|
|
788
|
-
|
|
1037
|
+
if (!shellConsumer && !uiOnly) {
|
|
1038
|
+
await readBackendManifest(distDirectory, identity);
|
|
1039
|
+
}
|
|
789
1040
|
const { artifacts, surfaces } = await createReleaseArtifactInputs(
|
|
790
1041
|
distDirectory,
|
|
791
1042
|
identity,
|
|
792
1043
|
target,
|
|
1044
|
+
apiOnly,
|
|
1045
|
+
uiOnly,
|
|
1046
|
+
shellConsumer,
|
|
793
1047
|
);
|
|
794
1048
|
const envelope = await createMicroVerticalReleaseEnvelope({
|
|
795
1049
|
artifactRoot: distDirectory,
|
|
796
1050
|
target,
|
|
797
1051
|
identity,
|
|
1052
|
+
...(ui ? { ui } : {}),
|
|
798
1053
|
artifacts,
|
|
799
|
-
surfaces,
|
|
1054
|
+
...releaseEnvelopeSurfaces(surfaces, shellConsumer),
|
|
800
1055
|
});
|
|
1056
|
+
await verifyBuildArtifactCarriers(distDirectory, envelope);
|
|
801
1057
|
await writeReleaseEnvelope(distDirectory, envelope);
|
|
802
1058
|
return envelope;
|
|
803
1059
|
};
|
|
@@ -806,13 +1062,15 @@ export const readFrameworkMicroVerticalReleaseEnvelope = async ({
|
|
|
806
1062
|
artifactRoot,
|
|
807
1063
|
expectedTarget,
|
|
808
1064
|
logicalPathForArtifact,
|
|
1065
|
+
expectedRendererProfile,
|
|
809
1066
|
required = false,
|
|
810
1067
|
}: {
|
|
811
1068
|
artifactRoot: string;
|
|
812
1069
|
expectedTarget?: MicroVerticalReleaseTarget;
|
|
813
1070
|
logicalPathForArtifact?: (artifact: MicroVerticalReleaseArtifact) => string;
|
|
1071
|
+
expectedRendererProfile?: RendererProfile;
|
|
814
1072
|
required?: boolean;
|
|
815
|
-
}): Promise<
|
|
1073
|
+
}): Promise<ReleaseEnvelope | undefined> => {
|
|
816
1074
|
const envelopePath = await resolveSafeReleaseEnvelopePath({
|
|
817
1075
|
artifactRoot,
|
|
818
1076
|
createDirectory: false,
|
|
@@ -825,32 +1083,113 @@ export const readFrameworkMicroVerticalReleaseEnvelope = async ({
|
|
|
825
1083
|
}
|
|
826
1084
|
return undefined;
|
|
827
1085
|
}
|
|
828
|
-
|
|
1086
|
+
const envelope = await verifyMicroVerticalReleaseEnvelope(
|
|
1087
|
+
await readJson(envelopePath),
|
|
1088
|
+
{
|
|
1089
|
+
artifactRoot,
|
|
1090
|
+
...(expectedTarget ? { expectedTarget } : {}),
|
|
1091
|
+
...(logicalPathForArtifact ? { logicalPathForArtifact } : {}),
|
|
1092
|
+
...(expectedRendererProfile ? { expectedRendererProfile } : {}),
|
|
1093
|
+
},
|
|
1094
|
+
);
|
|
1095
|
+
await verifyBuildArtifactCarriers(
|
|
829
1096
|
artifactRoot,
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
1097
|
+
envelope,
|
|
1098
|
+
logicalPathForArtifact,
|
|
1099
|
+
);
|
|
1100
|
+
return envelope;
|
|
1101
|
+
};
|
|
1102
|
+
|
|
1103
|
+
const verifyBuildArtifactCarriers = async (
|
|
1104
|
+
artifactRoot: string,
|
|
1105
|
+
envelope: ReleaseEnvelope,
|
|
1106
|
+
logicalPathForArtifact?: (artifact: MicroVerticalReleaseArtifact) => string,
|
|
1107
|
+
) => {
|
|
1108
|
+
const carriers = envelope.artifacts.filter(
|
|
1109
|
+
artifact =>
|
|
1110
|
+
artifact.logicalPath === ULTRAMODERN_BUILD_ARTIFACT_FILE ||
|
|
1111
|
+
artifact.logicalPath === PUBLIC_BUILD_ARTIFACT_PATH,
|
|
1112
|
+
);
|
|
1113
|
+
if (carriers.length === 0) {
|
|
1114
|
+
throw new Error(
|
|
1115
|
+
'[ultramodern-release-envelope] immutable build artifact must be bound to the release envelope.',
|
|
1116
|
+
);
|
|
1117
|
+
}
|
|
1118
|
+
for (const artifact of carriers) {
|
|
1119
|
+
if (
|
|
1120
|
+
artifact.kind !== 'file' ||
|
|
1121
|
+
artifact.runtime !== 'release-identity-metadata'
|
|
1122
|
+
) {
|
|
1123
|
+
throw new Error(
|
|
1124
|
+
'[ultramodern-release-envelope] immutable build artifact must be a real file with runtime "release-identity-metadata".',
|
|
1125
|
+
);
|
|
1126
|
+
}
|
|
1127
|
+
const logicalPath =
|
|
1128
|
+
logicalPathForArtifact?.(artifact) ?? artifact.logicalPath;
|
|
1129
|
+
const built = await readJson(path.join(artifactRoot, logicalPath));
|
|
1130
|
+
if (!isUltramodernBuildArtifact(built)) {
|
|
1131
|
+
throw new Error(
|
|
1132
|
+
'[ultramodern-release-envelope] bound build artifact is invalid.',
|
|
1133
|
+
);
|
|
1134
|
+
}
|
|
1135
|
+
assertIdentityBlock(
|
|
1136
|
+
built.deliveryUnit,
|
|
1137
|
+
envelope.identity,
|
|
1138
|
+
'buildArtifact.deliveryUnit',
|
|
1139
|
+
true,
|
|
1140
|
+
);
|
|
1141
|
+
if (Boolean(built.surfaces.ui) !== Boolean(envelope.ui)) {
|
|
1142
|
+
throw new Error(
|
|
1143
|
+
'[ultramodern-release-envelope] bound build artifact UI surface differs from the release envelope.',
|
|
1144
|
+
);
|
|
1145
|
+
}
|
|
1146
|
+
if (envelope.ui && built.surfaces.ui) {
|
|
1147
|
+
if (
|
|
1148
|
+
!isDeepStrictEqual(
|
|
1149
|
+
envelope.ui.routerBindings,
|
|
1150
|
+
built.surfaces.ui.routerBindings,
|
|
1151
|
+
)
|
|
1152
|
+
) {
|
|
1153
|
+
throw new Error(
|
|
1154
|
+
'[ultramodern-release-envelope] buildArtifact.ui.routerBindings differ from the release envelope.',
|
|
1155
|
+
);
|
|
1156
|
+
}
|
|
1157
|
+
assertRendererProfileCompatibility(
|
|
1158
|
+
envelope.ui.rendererProfile,
|
|
1159
|
+
built.surfaces.ui.rendererProfile,
|
|
1160
|
+
);
|
|
1161
|
+
for (const field of [
|
|
1162
|
+
'renderer',
|
|
1163
|
+
'protocolVersion',
|
|
1164
|
+
'appId',
|
|
1165
|
+
'entryName',
|
|
1166
|
+
'buildId',
|
|
1167
|
+
] as const) {
|
|
1168
|
+
if (
|
|
1169
|
+
envelope.ui.rendererIdentity[field] !==
|
|
1170
|
+
built.surfaces.ui.rendererIdentity[field]
|
|
1171
|
+
) {
|
|
1172
|
+
throw new Error(
|
|
1173
|
+
`[ultramodern-release-envelope] buildArtifact.ui.rendererIdentity.${field} differs from the release envelope.`,
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
}
|
|
1178
|
+
}
|
|
833
1179
|
};
|
|
834
1180
|
|
|
835
1181
|
export const verifyBuildOutputReleaseEnvelope = async (
|
|
836
1182
|
distDirectory: string,
|
|
837
1183
|
expectedTarget?: MicroVerticalReleaseTarget,
|
|
838
1184
|
) => {
|
|
839
|
-
const [hasEnvelope, hasBuildArtifact
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
pathExists(path.join(distDirectory, BACKEND_FEDERATION_MANIFEST_FILE)),
|
|
844
|
-
pathExists(
|
|
845
|
-
path.join(distDirectory, BACKEND_FEDERATION_REMOTE_ENTRY_FILE),
|
|
846
|
-
),
|
|
847
|
-
]);
|
|
848
|
-
const isFullStackMicroVertical =
|
|
849
|
-
hasBuildArtifact && (hasBackendManifest || hasBackendEntry);
|
|
1185
|
+
const [hasEnvelope, hasBuildArtifact] = await Promise.all([
|
|
1186
|
+
pathExists(path.join(distDirectory, MICROVERTICAL_RELEASE_ENVELOPE_PATH)),
|
|
1187
|
+
pathExists(path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE)),
|
|
1188
|
+
]);
|
|
850
1189
|
return readFrameworkMicroVerticalReleaseEnvelope({
|
|
851
1190
|
artifactRoot: distDirectory,
|
|
852
1191
|
...(expectedTarget ? { expectedTarget } : {}),
|
|
853
|
-
required: hasEnvelope ||
|
|
1192
|
+
required: hasEnvelope || hasBuildArtifact,
|
|
854
1193
|
});
|
|
855
1194
|
};
|
|
856
1195
|
|
|
@@ -869,13 +1208,24 @@ export const verifyNodeReleaseEnvelopeStaging = async ({
|
|
|
869
1208
|
const createNodeStagedReleaseArtifactInputs = async (
|
|
870
1209
|
outputDirectory: string,
|
|
871
1210
|
identity: MicroVerticalReleaseIdentity,
|
|
1211
|
+
apiOnly: boolean,
|
|
1212
|
+
uiOnly: boolean,
|
|
1213
|
+
declaredPublicAssets: readonly string[],
|
|
1214
|
+
shellConsumer: boolean,
|
|
872
1215
|
) => {
|
|
1216
|
+
const backendProducer = !uiOnly && !shellConsumer;
|
|
873
1217
|
const files = await collectFiles(outputDirectory);
|
|
1218
|
+
const declaredPublicAssetPaths = resolveDeclaredPublicAssets(
|
|
1219
|
+
files,
|
|
1220
|
+
declaredPublicAssets,
|
|
1221
|
+
'Node',
|
|
1222
|
+
);
|
|
874
1223
|
for (const requiredPath of [
|
|
875
1224
|
'index.js',
|
|
876
1225
|
'package.json',
|
|
877
|
-
|
|
878
|
-
|
|
1226
|
+
...(backendProducer
|
|
1227
|
+
? [BACKEND_FEDERATION_MANIFEST_FILE, BACKEND_FEDERATION_REMOTE_ENTRY_FILE]
|
|
1228
|
+
: []),
|
|
879
1229
|
]) {
|
|
880
1230
|
if (!files.includes(requiredPath)) {
|
|
881
1231
|
throw new Error(
|
|
@@ -888,12 +1238,24 @@ const createNodeStagedReleaseArtifactInputs = async (
|
|
|
888
1238
|
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
889
1239
|
BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
890
1240
|
]);
|
|
1241
|
+
if (
|
|
1242
|
+
shellConsumer &&
|
|
1243
|
+
files.some(logicalPath => backendFederationPaths.has(logicalPath))
|
|
1244
|
+
) {
|
|
1245
|
+
throw new Error(
|
|
1246
|
+
'[ultramodern-release-envelope] final Shell Node staging cannot contain a backend federation producer artifact.',
|
|
1247
|
+
);
|
|
1248
|
+
}
|
|
891
1249
|
const uiClientPaths = files.filter(logicalPath => {
|
|
892
1250
|
const topLevel = logicalPath.split('/')[0];
|
|
893
1251
|
return (
|
|
894
1252
|
topLevel === 'static' ||
|
|
895
1253
|
topLevel === 'html' ||
|
|
896
|
-
topLevel === 'public'
|
|
1254
|
+
(topLevel === 'public' &&
|
|
1255
|
+
logicalPath !== CRAWLER_POLICY_PATH &&
|
|
1256
|
+
logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH &&
|
|
1257
|
+
!isPublicMetadataPath(logicalPath) &&
|
|
1258
|
+
!declaredPublicAssetPaths.has(logicalPath)) ||
|
|
897
1259
|
[
|
|
898
1260
|
'index.html',
|
|
899
1261
|
'mf-manifest.json',
|
|
@@ -915,27 +1277,46 @@ const createNodeStagedReleaseArtifactInputs = async (
|
|
|
915
1277
|
(logicalPath.startsWith('api/') || logicalPath.startsWith('shared/')) &&
|
|
916
1278
|
COMPILED_MODULE_PATTERN.test(logicalPath),
|
|
917
1279
|
);
|
|
1280
|
+
if (
|
|
1281
|
+
uiOnly &&
|
|
1282
|
+
(apiBackendPaths.length > 0 ||
|
|
1283
|
+
files.some(logicalPath => backendFederationPaths.has(logicalPath)))
|
|
1284
|
+
)
|
|
1285
|
+
throw new Error(
|
|
1286
|
+
'[ultramodern-release-envelope] final UI-only Node staging contains an undeclared API/backend or backend federation artifact.',
|
|
1287
|
+
);
|
|
1288
|
+
if (
|
|
1289
|
+
apiOnly &&
|
|
1290
|
+
(uiClientPaths.length > 0 || ssrPaths.some(path => path !== 'index.js'))
|
|
1291
|
+
) {
|
|
1292
|
+
throw new Error(
|
|
1293
|
+
'[ultramodern-release-envelope] final API-only Node staging contains an undeclared UI/client or SSR surface.',
|
|
1294
|
+
);
|
|
1295
|
+
}
|
|
918
1296
|
for (const [surface, paths] of [
|
|
919
1297
|
['UI/client', uiClientPaths],
|
|
920
|
-
['Node SSR', ssrPaths],
|
|
1298
|
+
['Node SSR', apiOnly ? [] : ssrPaths],
|
|
921
1299
|
['Node API/backend', apiBackendPaths],
|
|
922
1300
|
] as const) {
|
|
923
|
-
if (
|
|
1301
|
+
if (
|
|
1302
|
+
paths.length === 0 &&
|
|
1303
|
+
(surface === 'Node API/backend' ? backendProducer : !apiOnly)
|
|
1304
|
+
) {
|
|
924
1305
|
throw new Error(
|
|
925
1306
|
`[ultramodern-release-envelope] final Node staging has no ${surface} artifacts.`,
|
|
926
1307
|
);
|
|
927
1308
|
}
|
|
928
1309
|
}
|
|
929
|
-
const clientExecutionPaths =
|
|
930
|
-
|
|
931
|
-
files
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
outputDirectory,
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
1310
|
+
const clientExecutionPaths = apiOnly
|
|
1311
|
+
? []
|
|
1312
|
+
: await manifestReferencedClientModules(outputDirectory, files);
|
|
1313
|
+
const ssrExecutionPaths = apiOnly
|
|
1314
|
+
? []
|
|
1315
|
+
: await routeReferencedSsrModules(outputDirectory, files, 'node');
|
|
1316
|
+
if (
|
|
1317
|
+
!apiOnly &&
|
|
1318
|
+
(clientExecutionPaths.length === 0 || ssrExecutionPaths.length === 0)
|
|
1319
|
+
) {
|
|
939
1320
|
throw new Error(
|
|
940
1321
|
'[ultramodern-release-envelope] final Node manifests must reference emitted UI/client and SSR execution modules.',
|
|
941
1322
|
);
|
|
@@ -949,33 +1330,57 @@ const createNodeStagedReleaseArtifactInputs = async (
|
|
|
949
1330
|
for (const logicalPath of uiClientPaths) {
|
|
950
1331
|
runtimeByPath.set(logicalPath, 'browser');
|
|
951
1332
|
}
|
|
952
|
-
for (const logicalPath of
|
|
1333
|
+
for (const logicalPath of declaredPublicAssetPaths) {
|
|
1334
|
+
runtimeByPath.set(logicalPath, DECLARED_PUBLIC_ASSET_RUNTIME);
|
|
1335
|
+
}
|
|
1336
|
+
if (files.includes(CRAWLER_POLICY_PATH)) {
|
|
1337
|
+
runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
1338
|
+
}
|
|
1339
|
+
for (const logicalPath of files.filter(isPublicMetadataPath)) {
|
|
1340
|
+
runtimeByPath.set(logicalPath, 'public-metadata');
|
|
1341
|
+
}
|
|
1342
|
+
for (const logicalPath of apiOnly ? [] : ssrPaths) {
|
|
953
1343
|
runtimeByPath.set(logicalPath, 'nodejs');
|
|
954
1344
|
}
|
|
955
1345
|
for (const logicalPath of apiBackendPaths) {
|
|
956
1346
|
runtimeByPath.set(logicalPath, 'nodejs');
|
|
957
1347
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
1348
|
+
if (backendProducer) {
|
|
1349
|
+
runtimeByPath.set(
|
|
1350
|
+
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
1351
|
+
'module-federation-manifest',
|
|
1352
|
+
);
|
|
1353
|
+
runtimeByPath.set(BACKEND_FEDERATION_REMOTE_ENTRY_FILE, 'nodejs');
|
|
1354
|
+
}
|
|
1355
|
+
for (const logicalPath of [
|
|
1356
|
+
ULTRAMODERN_BUILD_ARTIFACT_FILE,
|
|
1357
|
+
PUBLIC_BUILD_ARTIFACT_PATH,
|
|
1358
|
+
]) {
|
|
1359
|
+
if (files.includes(logicalPath))
|
|
1360
|
+
runtimeByPath.set(logicalPath, 'release-identity-metadata');
|
|
1361
|
+
}
|
|
963
1362
|
|
|
964
1363
|
const identityCarrierMetadata = await writeReleaseIdentityCarrierMetadata(
|
|
965
1364
|
outputDirectory,
|
|
966
1365
|
identity,
|
|
967
1366
|
{
|
|
968
|
-
apiBackend: apiBackendPaths,
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
1367
|
+
...(apiBackendPaths.length > 0 ? { apiBackend: apiBackendPaths } : {}),
|
|
1368
|
+
...(backendProducer
|
|
1369
|
+
? {
|
|
1370
|
+
backendFederation: [
|
|
1371
|
+
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
1372
|
+
BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
1373
|
+
],
|
|
1374
|
+
}
|
|
1375
|
+
: {}),
|
|
1376
|
+
ssr: apiOnly
|
|
1377
|
+
? []
|
|
1378
|
+
: [
|
|
1379
|
+
...new Set([
|
|
1380
|
+
...ssrExecutionPaths,
|
|
1381
|
+
...ssrPaths.filter(logicalPath => logicalPath !== 'index.js'),
|
|
1382
|
+
]),
|
|
1383
|
+
],
|
|
979
1384
|
uiClient: uiClientPaths.filter(logicalPath =>
|
|
980
1385
|
COMPILED_MODULE_PATTERN.test(logicalPath),
|
|
981
1386
|
),
|
|
@@ -992,22 +1397,31 @@ const createNodeStagedReleaseArtifactInputs = async (
|
|
|
992
1397
|
uiClient: [...uiClientPaths].sort((left, right) =>
|
|
993
1398
|
left.localeCompare(right),
|
|
994
1399
|
),
|
|
995
|
-
ssr:
|
|
1400
|
+
ssr: apiOnly
|
|
1401
|
+
? []
|
|
1402
|
+
: [...ssrPaths].sort((left, right) => left.localeCompare(right)),
|
|
996
1403
|
apiBackend: [...apiBackendPaths].sort((left, right) =>
|
|
997
1404
|
left.localeCompare(right),
|
|
998
1405
|
),
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1406
|
+
...(backendProducer
|
|
1407
|
+
? {
|
|
1408
|
+
backendFederation: {
|
|
1409
|
+
manifest: BACKEND_FEDERATION_MANIFEST_FILE,
|
|
1410
|
+
container: BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
1411
|
+
},
|
|
1412
|
+
}
|
|
1413
|
+
: {}),
|
|
1003
1414
|
},
|
|
1004
1415
|
};
|
|
1005
1416
|
};
|
|
1006
1417
|
|
|
1007
1418
|
export const emitNodeStagedReleaseEnvelope = async ({
|
|
1419
|
+
declaredPublicAssets = [],
|
|
1008
1420
|
distDirectory,
|
|
1009
1421
|
outputDirectory,
|
|
1010
1422
|
}: {
|
|
1423
|
+
/** Staged `deploy.node.publicAssets` files, relative to `outputDirectory`. */
|
|
1424
|
+
declaredPublicAssets?: readonly string[];
|
|
1011
1425
|
distDirectory: string;
|
|
1012
1426
|
outputDirectory: string;
|
|
1013
1427
|
}) => {
|
|
@@ -1015,17 +1429,24 @@ export const emitNodeStagedReleaseEnvelope = async ({
|
|
|
1015
1429
|
if (!source) {
|
|
1016
1430
|
return undefined;
|
|
1017
1431
|
}
|
|
1432
|
+
const shellConsumer = source.kind === SHELL_RELEASE_ENVELOPE_KIND;
|
|
1018
1433
|
const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(
|
|
1019
1434
|
outputDirectory,
|
|
1020
1435
|
source.identity,
|
|
1436
|
+
source.surfaces.uiClient.length === 0,
|
|
1437
|
+
source.surfaces.apiBackend.length === 0,
|
|
1438
|
+
declaredPublicAssets,
|
|
1439
|
+
shellConsumer,
|
|
1021
1440
|
);
|
|
1022
1441
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
1023
1442
|
artifactRoot: outputDirectory,
|
|
1024
1443
|
target: 'node',
|
|
1025
1444
|
identity: source.identity,
|
|
1445
|
+
...(source.ui ? { ui: source.ui } : {}),
|
|
1026
1446
|
artifacts,
|
|
1027
|
-
surfaces,
|
|
1447
|
+
...releaseEnvelopeSurfaces(surfaces, shellConsumer),
|
|
1028
1448
|
});
|
|
1449
|
+
await verifyBuildArtifactCarriers(outputDirectory, staged);
|
|
1029
1450
|
await writeReleaseEnvelope(outputDirectory, staged);
|
|
1030
1451
|
return staged;
|
|
1031
1452
|
};
|
|
@@ -1059,14 +1480,24 @@ export const stageCloudflareReleaseEnvelope = async ({
|
|
|
1059
1480
|
const createCloudflareStagedReleaseArtifactInputs = async (
|
|
1060
1481
|
outputDirectory: string,
|
|
1061
1482
|
identity: MicroVerticalReleaseIdentity,
|
|
1483
|
+
apiOnly: boolean,
|
|
1484
|
+
uiOnly: boolean,
|
|
1485
|
+
declaredPublicAssets: readonly string[],
|
|
1486
|
+
shellConsumer: boolean,
|
|
1062
1487
|
) => {
|
|
1488
|
+
const backendProducer = !uiOnly && !shellConsumer;
|
|
1063
1489
|
const files = await collectFiles(outputDirectory);
|
|
1490
|
+
const declaredPublicAssetPaths = resolveDeclaredPublicAssets(
|
|
1491
|
+
files,
|
|
1492
|
+
declaredPublicAssets,
|
|
1493
|
+
'Cloudflare',
|
|
1494
|
+
);
|
|
1064
1495
|
for (const requiredPath of [
|
|
1065
1496
|
'server/index.mjs',
|
|
1066
1497
|
'server/modern-worker-manifest.json',
|
|
1067
1498
|
'wrangler.json',
|
|
1068
1499
|
'package.json',
|
|
1069
|
-
'worker/package.json',
|
|
1500
|
+
...(apiOnly ? [] : ['worker/package.json']),
|
|
1070
1501
|
]) {
|
|
1071
1502
|
if (!files.includes(requiredPath)) {
|
|
1072
1503
|
throw new Error(
|
|
@@ -1087,34 +1518,84 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1087
1518
|
}
|
|
1088
1519
|
const backendManifestPath = `public/${BACKEND_FEDERATION_MANIFEST_FILE}`;
|
|
1089
1520
|
const backendContainerPath = `public/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`;
|
|
1090
|
-
const apiBackendPaths = files
|
|
1091
|
-
|
|
1092
|
-
|
|
1521
|
+
const apiBackendPaths = effectBffWorkerArtifacts(files);
|
|
1522
|
+
if (
|
|
1523
|
+
backendProducer &&
|
|
1524
|
+
!files.some(logicalPath => EFFECT_BFF_WORKER_PATTERN.test(logicalPath))
|
|
1525
|
+
) {
|
|
1526
|
+
throw new Error(
|
|
1527
|
+
'[ultramodern-release-envelope] final Cloudflare staging has no actual Effect API/BFF worker artifact.',
|
|
1528
|
+
);
|
|
1529
|
+
}
|
|
1093
1530
|
const ssrPaths = files.filter(
|
|
1094
1531
|
logicalPath =>
|
|
1095
1532
|
(logicalPath.startsWith('server/') ||
|
|
1096
1533
|
logicalPath.startsWith('worker/')) &&
|
|
1097
1534
|
COMPILED_MODULE_PATTERN.test(logicalPath) &&
|
|
1098
|
-
!
|
|
1535
|
+
!apiBackendPaths.includes(logicalPath),
|
|
1099
1536
|
);
|
|
1100
1537
|
const uiClientPaths = files.filter(
|
|
1101
1538
|
logicalPath =>
|
|
1102
1539
|
logicalPath.startsWith('public/') &&
|
|
1540
|
+
logicalPath !== CRAWLER_POLICY_PATH &&
|
|
1541
|
+
!isPublicMetadataPath(logicalPath) &&
|
|
1542
|
+
!declaredPublicAssetPaths.has(logicalPath) &&
|
|
1543
|
+
logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH &&
|
|
1103
1544
|
logicalPath !== backendManifestPath &&
|
|
1104
1545
|
logicalPath !== backendContainerPath,
|
|
1105
1546
|
);
|
|
1547
|
+
if (
|
|
1548
|
+
shellConsumer &&
|
|
1549
|
+
files.some(
|
|
1550
|
+
logicalPath =>
|
|
1551
|
+
logicalPath === backendManifestPath ||
|
|
1552
|
+
logicalPath === backendContainerPath,
|
|
1553
|
+
)
|
|
1554
|
+
) {
|
|
1555
|
+
throw new Error(
|
|
1556
|
+
'[ultramodern-release-envelope] final Shell Cloudflare staging cannot contain a backend federation producer artifact.',
|
|
1557
|
+
);
|
|
1558
|
+
}
|
|
1559
|
+
if (
|
|
1560
|
+
uiOnly &&
|
|
1561
|
+
(apiBackendPaths.length > 0 ||
|
|
1562
|
+
files.some(
|
|
1563
|
+
logicalPath =>
|
|
1564
|
+
logicalPath === backendManifestPath ||
|
|
1565
|
+
logicalPath === backendContainerPath ||
|
|
1566
|
+
(logicalPath.startsWith('api/') &&
|
|
1567
|
+
COMPILED_MODULE_PATTERN.test(logicalPath)),
|
|
1568
|
+
))
|
|
1569
|
+
)
|
|
1570
|
+
throw new Error(
|
|
1571
|
+
'[ultramodern-release-envelope] final UI-only Cloudflare staging contains an undeclared API/backend or backend federation artifact.',
|
|
1572
|
+
);
|
|
1573
|
+
if (
|
|
1574
|
+
apiOnly &&
|
|
1575
|
+
(uiClientPaths.length > 0 ||
|
|
1576
|
+
ssrPaths.some(path => path !== 'server/index.mjs'))
|
|
1577
|
+
) {
|
|
1578
|
+
throw new Error(
|
|
1579
|
+
'[ultramodern-release-envelope] final API-only Cloudflare staging contains an undeclared UI/client or SSR surface.',
|
|
1580
|
+
);
|
|
1581
|
+
}
|
|
1106
1582
|
for (const [surface, paths] of [
|
|
1107
1583
|
['UI/client', uiClientPaths],
|
|
1108
|
-
['Cloudflare SSR', ssrPaths],
|
|
1584
|
+
['Cloudflare SSR', apiOnly ? [] : ssrPaths],
|
|
1109
1585
|
['Cloudflare API/backend', apiBackendPaths],
|
|
1110
1586
|
] as const) {
|
|
1111
|
-
if (
|
|
1587
|
+
if (
|
|
1588
|
+
paths.length === 0 &&
|
|
1589
|
+
(surface === 'Cloudflare API/backend' ? backendProducer : !apiOnly)
|
|
1590
|
+
) {
|
|
1112
1591
|
throw new Error(
|
|
1113
1592
|
`[ultramodern-release-envelope] final Cloudflare staging has no ${surface} artifacts.`,
|
|
1114
1593
|
);
|
|
1115
1594
|
}
|
|
1116
1595
|
}
|
|
1117
|
-
for (const logicalPath of
|
|
1596
|
+
for (const logicalPath of backendProducer
|
|
1597
|
+
? [backendManifestPath, backendContainerPath]
|
|
1598
|
+
: []) {
|
|
1118
1599
|
if (!files.includes(logicalPath)) {
|
|
1119
1600
|
throw new Error(
|
|
1120
1601
|
`[ultramodern-release-envelope] final Cloudflare staging is missing ${logicalPath}.`,
|
|
@@ -1122,18 +1603,25 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1122
1603
|
}
|
|
1123
1604
|
}
|
|
1124
1605
|
|
|
1125
|
-
const clientExecutionPaths =
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1606
|
+
const clientExecutionPaths = apiOnly
|
|
1607
|
+
? []
|
|
1608
|
+
: await manifestReferencedClientModules(
|
|
1609
|
+
outputDirectory,
|
|
1610
|
+
files,
|
|
1611
|
+
'public/mf-manifest.json',
|
|
1612
|
+
);
|
|
1613
|
+
const ssrExecutionPaths = apiOnly
|
|
1614
|
+
? []
|
|
1615
|
+
: await routeReferencedSsrModules(
|
|
1616
|
+
outputDirectory,
|
|
1617
|
+
files,
|
|
1618
|
+
'cloudflare',
|
|
1619
|
+
'server/route.json',
|
|
1620
|
+
);
|
|
1621
|
+
if (
|
|
1622
|
+
!apiOnly &&
|
|
1623
|
+
(clientExecutionPaths.length === 0 || ssrExecutionPaths.length === 0)
|
|
1624
|
+
) {
|
|
1137
1625
|
throw new Error(
|
|
1138
1626
|
'[ultramodern-release-envelope] final Cloudflare manifests must reference emitted UI/client and SSR execution modules.',
|
|
1139
1627
|
);
|
|
@@ -1149,23 +1637,44 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1149
1637
|
}
|
|
1150
1638
|
};
|
|
1151
1639
|
add(uiClientPaths, 'browser');
|
|
1152
|
-
add(
|
|
1640
|
+
add([...declaredPublicAssetPaths], DECLARED_PUBLIC_ASSET_RUNTIME);
|
|
1641
|
+
if (files.includes(CRAWLER_POLICY_PATH)) {
|
|
1642
|
+
runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
1643
|
+
}
|
|
1644
|
+
add(files.filter(isPublicMetadataPath), 'public-metadata');
|
|
1645
|
+
add(apiOnly ? [] : ssrPaths, 'workerd');
|
|
1153
1646
|
add(apiBackendPaths, 'workerd-effect');
|
|
1154
|
-
|
|
1155
|
-
|
|
1647
|
+
if (backendProducer) {
|
|
1648
|
+
add([backendManifestPath], 'module-federation-manifest');
|
|
1649
|
+
add([backendContainerPath], 'commonjs-module');
|
|
1650
|
+
}
|
|
1651
|
+
add(
|
|
1652
|
+
files.filter(
|
|
1653
|
+
logicalPath =>
|
|
1654
|
+
logicalPath === ULTRAMODERN_BUILD_ARTIFACT_FILE ||
|
|
1655
|
+
logicalPath === PUBLIC_BUILD_ARTIFACT_PATH,
|
|
1656
|
+
),
|
|
1657
|
+
'release-identity-metadata',
|
|
1658
|
+
);
|
|
1156
1659
|
|
|
1157
1660
|
const identityCarrierMetadata = await writeReleaseIdentityCarrierMetadata(
|
|
1158
1661
|
outputDirectory,
|
|
1159
1662
|
identity,
|
|
1160
1663
|
{
|
|
1161
|
-
apiBackend: apiBackendPaths,
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1664
|
+
...(apiBackendPaths.length > 0 ? { apiBackend: apiBackendPaths } : {}),
|
|
1665
|
+
...(backendProducer
|
|
1666
|
+
? { backendFederation: [backendManifestPath, backendContainerPath] }
|
|
1667
|
+
: {}),
|
|
1668
|
+
ssr: apiOnly
|
|
1669
|
+
? []
|
|
1670
|
+
: [
|
|
1671
|
+
...new Set([
|
|
1672
|
+
...ssrExecutionPaths,
|
|
1673
|
+
...ssrPaths.filter(logicalPath =>
|
|
1674
|
+
logicalPath.startsWith('worker/'),
|
|
1675
|
+
),
|
|
1676
|
+
]),
|
|
1677
|
+
],
|
|
1169
1678
|
uiClient: uiClientPaths.filter(logicalPath =>
|
|
1170
1679
|
COMPILED_MODULE_PATTERN.test(logicalPath),
|
|
1171
1680
|
),
|
|
@@ -1182,22 +1691,31 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1182
1691
|
uiClient: [...uiClientPaths].sort((left, right) =>
|
|
1183
1692
|
left.localeCompare(right),
|
|
1184
1693
|
),
|
|
1185
|
-
ssr:
|
|
1694
|
+
ssr: apiOnly
|
|
1695
|
+
? []
|
|
1696
|
+
: [...ssrPaths].sort((left, right) => left.localeCompare(right)),
|
|
1186
1697
|
apiBackend: [...apiBackendPaths].sort((left, right) =>
|
|
1187
1698
|
left.localeCompare(right),
|
|
1188
1699
|
),
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1700
|
+
...(backendProducer
|
|
1701
|
+
? {
|
|
1702
|
+
backendFederation: {
|
|
1703
|
+
manifest: backendManifestPath,
|
|
1704
|
+
container: backendContainerPath,
|
|
1705
|
+
},
|
|
1706
|
+
}
|
|
1707
|
+
: {}),
|
|
1193
1708
|
},
|
|
1194
1709
|
};
|
|
1195
1710
|
};
|
|
1196
1711
|
|
|
1197
1712
|
export const emitCloudflareStagedReleaseEnvelope = async ({
|
|
1713
|
+
declaredPublicAssets = [],
|
|
1198
1714
|
distDirectory,
|
|
1199
1715
|
outputDirectory,
|
|
1200
1716
|
}: {
|
|
1717
|
+
/** Staged `deploy.worker.publicAssets` files, relative to `outputDirectory`. */
|
|
1718
|
+
declaredPublicAssets?: readonly string[];
|
|
1201
1719
|
distDirectory: string;
|
|
1202
1720
|
outputDirectory: string;
|
|
1203
1721
|
}) => {
|
|
@@ -1208,18 +1726,25 @@ export const emitCloudflareStagedReleaseEnvelope = async ({
|
|
|
1208
1726
|
if (!source) {
|
|
1209
1727
|
return undefined;
|
|
1210
1728
|
}
|
|
1729
|
+
const shellConsumer = source.kind === SHELL_RELEASE_ENVELOPE_KIND;
|
|
1211
1730
|
const { artifacts, surfaces } =
|
|
1212
1731
|
await createCloudflareStagedReleaseArtifactInputs(
|
|
1213
1732
|
outputDirectory,
|
|
1214
1733
|
source.identity,
|
|
1734
|
+
source.surfaces.uiClient.length === 0,
|
|
1735
|
+
source.surfaces.apiBackend.length === 0,
|
|
1736
|
+
declaredPublicAssets,
|
|
1737
|
+
shellConsumer,
|
|
1215
1738
|
);
|
|
1216
1739
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
1217
1740
|
artifactRoot: outputDirectory,
|
|
1218
1741
|
target: 'cloudflare',
|
|
1219
1742
|
identity: source.identity,
|
|
1743
|
+
...(source.ui ? { ui: source.ui } : {}),
|
|
1220
1744
|
artifacts,
|
|
1221
|
-
surfaces,
|
|
1745
|
+
...releaseEnvelopeSurfaces(surfaces, shellConsumer),
|
|
1222
1746
|
});
|
|
1747
|
+
await verifyBuildArtifactCarriers(outputDirectory, staged);
|
|
1223
1748
|
await writeReleaseEnvelope(outputDirectory, staged);
|
|
1224
1749
|
return staged;
|
|
1225
1750
|
};
|