@bleedingdev/modern-js-app-tools-extensions 3.9.0-ultramodern.12 → 3.9.0-ultramodern.13
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/dist/cjs/backend-federation-build/codegen.js +7 -2
- package/dist/cjs/backend-federation-build/config.js +86 -36
- package/dist/cjs/backend-federation-build/emit.js +17 -13
- package/dist/cjs/cloudflare/delivery-unit.js +16 -26
- package/dist/cjs/cloudflare/index.js +7 -1
- package/dist/cjs/cloudflare-builder.js +13 -3
- package/dist/cjs/cloudflare-output-contract.js +1 -0
- package/dist/cjs/cloudflare-output-verifier/identity.js +26 -24
- 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/deploy-output/npmAliases.js +28 -3
- package/dist/cjs/release-envelope/framework-output.js +62 -49
- package/dist/cjs/release-envelope/index.js +10 -4
- package/dist/esm/backend-federation-build/codegen.mjs +7 -2
- package/dist/esm/backend-federation-build/config.mjs +78 -31
- package/dist/esm/backend-federation-build/emit.mjs +18 -14
- package/dist/esm/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm/cloudflare/index.mjs +7 -1
- package/dist/esm/cloudflare-builder.mjs +13 -3
- package/dist/esm/cloudflare-output-contract.mjs +1 -0
- package/dist/esm/cloudflare-output-verifier/identity.mjs +26 -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/deploy-output/npmAliases.mjs +28 -3
- package/dist/esm/release-envelope/framework-output.mjs +62 -49
- package/dist/esm/release-envelope/index.mjs +10 -4
- package/dist/esm-node/backend-federation-build/codegen.mjs +7 -2
- package/dist/esm-node/backend-federation-build/config.mjs +78 -31
- package/dist/esm-node/backend-federation-build/emit.mjs +18 -14
- package/dist/esm-node/cloudflare/delivery-unit.mjs +16 -26
- package/dist/esm-node/cloudflare/index.mjs +7 -1
- package/dist/esm-node/cloudflare-builder.mjs +13 -3
- package/dist/esm-node/cloudflare-output-contract.mjs +1 -0
- package/dist/esm-node/cloudflare-output-verifier/identity.mjs +26 -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/deploy-output/npmAliases.mjs +28 -3
- package/dist/esm-node/release-envelope/framework-output.mjs +62 -49
- package/dist/esm-node/release-envelope/index.mjs +10 -4
- package/dist/types/backend-federation-build/codegen.d.ts +30 -0
- package/dist/types/backend-federation-build/config.d.ts +32 -13
- package/dist/types/cloudflare/delivery-unit.d.ts +1 -1
- package/dist/types/cloudflare-builder.d.ts +1 -0
- package/dist/types/cloudflare-output-contract.d.ts +1 -1
- package/package.json +20 -20
- package/src/backend-federation-build/codegen.ts +6 -2
- package/src/backend-federation-build/config.ts +207 -71
- package/src/backend-federation-build/emit.ts +33 -22
- package/src/cloudflare/delivery-unit.ts +39 -33
- package/src/cloudflare/index.ts +20 -6
- package/src/cloudflare-builder.ts +18 -4
- package/src/cloudflare-output-contract.ts +1 -0
- package/src/cloudflare-output-verifier/identity.ts +4 -0
- 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/deploy-output/npmAliases.ts +52 -3
- package/src/release-envelope/framework-output.ts +163 -90
- package/src/release-envelope/index.ts +26 -5
|
@@ -33,6 +33,16 @@ const MICROVERTICAL_RELEASE_IDENTITY_CARRIERS_SCHEMA_VERSION = 1;
|
|
|
33
33
|
|
|
34
34
|
const COMPILED_MODULE_PATTERN = /\.(?:c|m)?js$/u;
|
|
35
35
|
const EFFECT_BFF_WORKER_PATTERN = /^worker\/__modern_bff_effect\.(?:c|m)?js$/u;
|
|
36
|
+
const EFFECT_BFF_WORKER_SUPPORT_PATTERN =
|
|
37
|
+
/^worker\/__modern_worker_(?:runtime|shared)_*\.(?:c|m)?js$/u;
|
|
38
|
+
const isEffectBffWorkerArtifact = (logicalPath: string) =>
|
|
39
|
+
EFFECT_BFF_WORKER_PATTERN.test(logicalPath) ||
|
|
40
|
+
EFFECT_BFF_WORKER_SUPPORT_PATTERN.test(logicalPath);
|
|
41
|
+
const CRAWLER_POLICY_PATH = 'public/robots.txt';
|
|
42
|
+
const isPublicMetadataPath = (logicalPath: string) =>
|
|
43
|
+
logicalPath === 'public/_headers' ||
|
|
44
|
+
/^public\/\.well-known\/[^/]+\.json$/u.test(logicalPath);
|
|
45
|
+
const PUBLIC_BUILD_ARTIFACT_PATH = `public/${ULTRAMODERN_BUILD_ARTIFACT_FILE}`;
|
|
36
46
|
|
|
37
47
|
const isRecord = (value: unknown): value is Record<string, unknown> =>
|
|
38
48
|
typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
@@ -345,7 +355,7 @@ const readBuildArtifact = async (
|
|
|
345
355
|
return undefined;
|
|
346
356
|
}
|
|
347
357
|
throw new Error(
|
|
348
|
-
'[ultramodern-release-envelope] sourceRevision "workspace" cannot produce a promotable
|
|
358
|
+
'[ultramodern-release-envelope] sourceRevision "workspace" cannot produce a promotable envelope.',
|
|
349
359
|
);
|
|
350
360
|
}
|
|
351
361
|
return artifact;
|
|
@@ -516,7 +526,11 @@ const writeReleaseIdentityCarrierMetadata = async (
|
|
|
516
526
|
ReleaseIdentityCarrierSurface,
|
|
517
527
|
string[],
|
|
518
528
|
][]) {
|
|
519
|
-
if (
|
|
529
|
+
if (
|
|
530
|
+
logicalPaths.length === 0 &&
|
|
531
|
+
surface !== 'ssr' &&
|
|
532
|
+
surface !== 'uiClient'
|
|
533
|
+
) {
|
|
520
534
|
throw new Error(
|
|
521
535
|
`[ultramodern-release-envelope] ${surface} has no declared release-identity carrier artifact.`,
|
|
522
536
|
);
|
|
@@ -591,12 +605,10 @@ const createReleaseArtifactInputs = async (
|
|
|
591
605
|
distDirectory: string,
|
|
592
606
|
identity: MicroVerticalReleaseIdentity,
|
|
593
607
|
target: MicroVerticalReleaseTarget,
|
|
608
|
+
apiOnly: boolean,
|
|
594
609
|
) => {
|
|
595
610
|
const files = await collectFiles(distDirectory);
|
|
596
|
-
if (
|
|
597
|
-
target === 'node' &&
|
|
598
|
-
files.some(logicalPath => EFFECT_BFF_WORKER_PATTERN.test(logicalPath))
|
|
599
|
-
) {
|
|
611
|
+
if (target === 'node' && files.some(isEffectBffWorkerArtifact)) {
|
|
600
612
|
throw new Error(
|
|
601
613
|
'[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.',
|
|
602
614
|
);
|
|
@@ -606,7 +618,9 @@ const createReleaseArtifactInputs = async (
|
|
|
606
618
|
return (
|
|
607
619
|
topLevel === 'static' ||
|
|
608
620
|
topLevel === 'html' ||
|
|
609
|
-
topLevel === 'public'
|
|
621
|
+
(topLevel === 'public' &&
|
|
622
|
+
logicalPath !== CRAWLER_POLICY_PATH &&
|
|
623
|
+
!isPublicMetadataPath(logicalPath)) ||
|
|
610
624
|
[
|
|
611
625
|
'index.html',
|
|
612
626
|
'mf-manifest.json',
|
|
@@ -628,7 +642,7 @@ const createReleaseArtifactInputs = async (
|
|
|
628
642
|
logicalPath =>
|
|
629
643
|
logicalPath.startsWith('worker/') &&
|
|
630
644
|
COMPILED_MODULE_PATTERN.test(logicalPath) &&
|
|
631
|
-
!
|
|
645
|
+
!isEffectBffWorkerArtifact(logicalPath),
|
|
632
646
|
);
|
|
633
647
|
const nodeApiEntryPaths = files.filter(
|
|
634
648
|
logicalPath =>
|
|
@@ -643,44 +657,48 @@ const createReleaseArtifactInputs = async (
|
|
|
643
657
|
logicalPath.startsWith('shared/')) &&
|
|
644
658
|
COMPILED_MODULE_PATTERN.test(logicalPath),
|
|
645
659
|
)
|
|
646
|
-
: files.filter(
|
|
647
|
-
EFFECT_BFF_WORKER_PATTERN.test(logicalPath),
|
|
648
|
-
);
|
|
660
|
+
: files.filter(isEffectBffWorkerArtifact);
|
|
649
661
|
|
|
650
|
-
if (uiClientPaths.length
|
|
662
|
+
if (apiOnly && (uiClientPaths.length > 0 || ssrPaths.length > 0)) {
|
|
663
|
+
throw new Error(
|
|
664
|
+
'[ultramodern-release-envelope] API-only MicroVertical emitted an undeclared UI/client or SSR surface.',
|
|
665
|
+
);
|
|
666
|
+
}
|
|
667
|
+
if (!apiOnly && uiClientPaths.length === 0) {
|
|
651
668
|
throw new Error(
|
|
652
669
|
`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no UI/client artifacts.`,
|
|
653
670
|
);
|
|
654
671
|
}
|
|
655
|
-
if (ssrPaths.length === 0) {
|
|
672
|
+
if (!apiOnly && ssrPaths.length === 0) {
|
|
656
673
|
throw new Error(
|
|
657
674
|
`[ultramodern-release-envelope] ${target} full-stack MicroVertical has no ${target === 'node' ? 'Node' : 'workerd'} SSR artifacts.`,
|
|
658
675
|
);
|
|
659
676
|
}
|
|
660
677
|
if (
|
|
661
678
|
(target === 'node' && nodeApiEntryPaths.length === 0) ||
|
|
679
|
+
(target === 'cloudflare' &&
|
|
680
|
+
!files.some(logicalPath =>
|
|
681
|
+
EFFECT_BFF_WORKER_PATTERN.test(logicalPath),
|
|
682
|
+
)) ||
|
|
662
683
|
apiBackendPaths.length === 0
|
|
663
684
|
) {
|
|
664
685
|
throw new Error(
|
|
665
|
-
`[ultramodern-release-envelope] ${target}
|
|
686
|
+
`[ultramodern-release-envelope] ${target} MicroVertical has no actual ${target === 'node' ? 'compiled Node Effect API artifact' : 'Effect API/BFF worker artifact'}.`,
|
|
666
687
|
);
|
|
667
688
|
}
|
|
668
689
|
|
|
669
|
-
const clientExecutionPaths =
|
|
670
|
-
|
|
671
|
-
files
|
|
672
|
-
)
|
|
673
|
-
if (clientExecutionPaths.length === 0) {
|
|
690
|
+
const clientExecutionPaths = apiOnly
|
|
691
|
+
? []
|
|
692
|
+
: await manifestReferencedClientModules(distDirectory, files);
|
|
693
|
+
if (!apiOnly && clientExecutionPaths.length === 0) {
|
|
674
694
|
throw new Error(
|
|
675
695
|
'[ultramodern-release-envelope] UI/client manifest references no compiled execution module.',
|
|
676
696
|
);
|
|
677
697
|
}
|
|
678
|
-
const ssrExecutionPaths =
|
|
679
|
-
|
|
680
|
-
files,
|
|
681
|
-
|
|
682
|
-
);
|
|
683
|
-
if (ssrExecutionPaths.length === 0) {
|
|
698
|
+
const ssrExecutionPaths = apiOnly
|
|
699
|
+
? []
|
|
700
|
+
: await routeReferencedSsrModules(distDirectory, files, target);
|
|
701
|
+
if (!apiOnly && ssrExecutionPaths.length === 0) {
|
|
684
702
|
throw new Error(
|
|
685
703
|
`[ultramodern-release-envelope] route manifest references no emitted ${target === 'node' ? 'Node' : 'Cloudflare'} SSR execution module.`,
|
|
686
704
|
);
|
|
@@ -699,6 +717,11 @@ const createReleaseArtifactInputs = async (
|
|
|
699
717
|
}
|
|
700
718
|
};
|
|
701
719
|
add(uiClientPaths, 'browser');
|
|
720
|
+
add(
|
|
721
|
+
files.filter(logicalPath => logicalPath === CRAWLER_POLICY_PATH),
|
|
722
|
+
'crawler-policy',
|
|
723
|
+
);
|
|
724
|
+
add(files.filter(isPublicMetadataPath), 'public-metadata');
|
|
702
725
|
add(ssrPaths, target === 'node' ? 'nodejs' : 'workerd');
|
|
703
726
|
add(apiBackendPaths, target === 'node' ? 'nodejs' : 'workerd-effect');
|
|
704
727
|
add([BACKEND_FEDERATION_MANIFEST_FILE], 'module-federation-manifest');
|
|
@@ -766,9 +789,6 @@ export const emitFrameworkMicroVerticalReleaseEnvelope = async ({
|
|
|
766
789
|
requirePromotable?: boolean;
|
|
767
790
|
target: MicroVerticalReleaseTarget;
|
|
768
791
|
}): Promise<MicroVerticalReleaseEnvelope | undefined> => {
|
|
769
|
-
if (apiOnly) {
|
|
770
|
-
return undefined;
|
|
771
|
-
}
|
|
772
792
|
const backendManifestPath = path.join(
|
|
773
793
|
distDirectory,
|
|
774
794
|
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
@@ -781,12 +801,12 @@ export const emitFrameworkMicroVerticalReleaseEnvelope = async ({
|
|
|
781
801
|
pathExists(backendManifestPath),
|
|
782
802
|
pathExists(backendContainerPath),
|
|
783
803
|
]);
|
|
784
|
-
if (!hasBackendManifest && !hasBackendContainer) {
|
|
804
|
+
if (!hasBackendManifest && !hasBackendContainer && !apiOnly) {
|
|
785
805
|
return undefined;
|
|
786
806
|
}
|
|
787
807
|
if (!hasBackendManifest || !hasBackendContainer) {
|
|
788
808
|
throw new Error(
|
|
789
|
-
'[ultramodern-release-envelope]
|
|
809
|
+
'[ultramodern-release-envelope] MicroVertical backend federation manifest and container must be emitted together.',
|
|
790
810
|
);
|
|
791
811
|
}
|
|
792
812
|
|
|
@@ -808,6 +828,7 @@ export const emitFrameworkMicroVerticalReleaseEnvelope = async ({
|
|
|
808
828
|
distDirectory,
|
|
809
829
|
identity,
|
|
810
830
|
target,
|
|
831
|
+
apiOnly,
|
|
811
832
|
);
|
|
812
833
|
const envelope = await createMicroVerticalReleaseEnvelope({
|
|
813
834
|
artifactRoot: distDirectory,
|
|
@@ -854,21 +875,14 @@ export const verifyBuildOutputReleaseEnvelope = async (
|
|
|
854
875
|
distDirectory: string,
|
|
855
876
|
expectedTarget?: MicroVerticalReleaseTarget,
|
|
856
877
|
) => {
|
|
857
|
-
const [hasEnvelope, hasBuildArtifact
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
pathExists(path.join(distDirectory, BACKEND_FEDERATION_MANIFEST_FILE)),
|
|
862
|
-
pathExists(
|
|
863
|
-
path.join(distDirectory, BACKEND_FEDERATION_REMOTE_ENTRY_FILE),
|
|
864
|
-
),
|
|
865
|
-
]);
|
|
866
|
-
const isFullStackMicroVertical =
|
|
867
|
-
hasBuildArtifact && (hasBackendManifest || hasBackendEntry);
|
|
878
|
+
const [hasEnvelope, hasBuildArtifact] = await Promise.all([
|
|
879
|
+
pathExists(path.join(distDirectory, MICROVERTICAL_RELEASE_ENVELOPE_PATH)),
|
|
880
|
+
pathExists(path.join(distDirectory, ULTRAMODERN_BUILD_ARTIFACT_FILE)),
|
|
881
|
+
]);
|
|
868
882
|
return readFrameworkMicroVerticalReleaseEnvelope({
|
|
869
883
|
artifactRoot: distDirectory,
|
|
870
884
|
...(expectedTarget ? { expectedTarget } : {}),
|
|
871
|
-
required: hasEnvelope ||
|
|
885
|
+
required: hasEnvelope || hasBuildArtifact,
|
|
872
886
|
});
|
|
873
887
|
};
|
|
874
888
|
|
|
@@ -887,6 +901,7 @@ export const verifyNodeReleaseEnvelopeStaging = async ({
|
|
|
887
901
|
const createNodeStagedReleaseArtifactInputs = async (
|
|
888
902
|
outputDirectory: string,
|
|
889
903
|
identity: MicroVerticalReleaseIdentity,
|
|
904
|
+
apiOnly: boolean,
|
|
890
905
|
) => {
|
|
891
906
|
const files = await collectFiles(outputDirectory);
|
|
892
907
|
for (const requiredPath of [
|
|
@@ -911,7 +926,9 @@ const createNodeStagedReleaseArtifactInputs = async (
|
|
|
911
926
|
return (
|
|
912
927
|
topLevel === 'static' ||
|
|
913
928
|
topLevel === 'html' ||
|
|
914
|
-
topLevel === 'public'
|
|
929
|
+
(topLevel === 'public' &&
|
|
930
|
+
logicalPath !== CRAWLER_POLICY_PATH &&
|
|
931
|
+
!isPublicMetadataPath(logicalPath)) ||
|
|
915
932
|
[
|
|
916
933
|
'index.html',
|
|
917
934
|
'mf-manifest.json',
|
|
@@ -933,27 +950,35 @@ const createNodeStagedReleaseArtifactInputs = async (
|
|
|
933
950
|
(logicalPath.startsWith('api/') || logicalPath.startsWith('shared/')) &&
|
|
934
951
|
COMPILED_MODULE_PATTERN.test(logicalPath),
|
|
935
952
|
);
|
|
953
|
+
if (
|
|
954
|
+
apiOnly &&
|
|
955
|
+
(uiClientPaths.length > 0 || ssrPaths.some(path => path !== 'index.js'))
|
|
956
|
+
) {
|
|
957
|
+
throw new Error(
|
|
958
|
+
'[ultramodern-release-envelope] final API-only Node staging contains an undeclared UI/client or SSR surface.',
|
|
959
|
+
);
|
|
960
|
+
}
|
|
936
961
|
for (const [surface, paths] of [
|
|
937
962
|
['UI/client', uiClientPaths],
|
|
938
|
-
['Node SSR', ssrPaths],
|
|
963
|
+
['Node SSR', apiOnly ? [] : ssrPaths],
|
|
939
964
|
['Node API/backend', apiBackendPaths],
|
|
940
965
|
] as const) {
|
|
941
|
-
if (paths.length === 0) {
|
|
966
|
+
if (paths.length === 0 && (!apiOnly || surface === 'Node API/backend')) {
|
|
942
967
|
throw new Error(
|
|
943
968
|
`[ultramodern-release-envelope] final Node staging has no ${surface} artifacts.`,
|
|
944
969
|
);
|
|
945
970
|
}
|
|
946
971
|
}
|
|
947
|
-
const clientExecutionPaths =
|
|
948
|
-
|
|
949
|
-
files
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
outputDirectory,
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
972
|
+
const clientExecutionPaths = apiOnly
|
|
973
|
+
? []
|
|
974
|
+
: await manifestReferencedClientModules(outputDirectory, files);
|
|
975
|
+
const ssrExecutionPaths = apiOnly
|
|
976
|
+
? []
|
|
977
|
+
: await routeReferencedSsrModules(outputDirectory, files, 'node');
|
|
978
|
+
if (
|
|
979
|
+
!apiOnly &&
|
|
980
|
+
(clientExecutionPaths.length === 0 || ssrExecutionPaths.length === 0)
|
|
981
|
+
) {
|
|
957
982
|
throw new Error(
|
|
958
983
|
'[ultramodern-release-envelope] final Node manifests must reference emitted UI/client and SSR execution modules.',
|
|
959
984
|
);
|
|
@@ -967,7 +992,13 @@ const createNodeStagedReleaseArtifactInputs = async (
|
|
|
967
992
|
for (const logicalPath of uiClientPaths) {
|
|
968
993
|
runtimeByPath.set(logicalPath, 'browser');
|
|
969
994
|
}
|
|
970
|
-
|
|
995
|
+
if (files.includes(CRAWLER_POLICY_PATH)) {
|
|
996
|
+
runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
997
|
+
}
|
|
998
|
+
for (const logicalPath of files.filter(isPublicMetadataPath)) {
|
|
999
|
+
runtimeByPath.set(logicalPath, 'public-metadata');
|
|
1000
|
+
}
|
|
1001
|
+
for (const logicalPath of apiOnly ? [] : ssrPaths) {
|
|
971
1002
|
runtimeByPath.set(logicalPath, 'nodejs');
|
|
972
1003
|
}
|
|
973
1004
|
for (const logicalPath of apiBackendPaths) {
|
|
@@ -988,12 +1019,14 @@ const createNodeStagedReleaseArtifactInputs = async (
|
|
|
988
1019
|
BACKEND_FEDERATION_MANIFEST_FILE,
|
|
989
1020
|
BACKEND_FEDERATION_REMOTE_ENTRY_FILE,
|
|
990
1021
|
],
|
|
991
|
-
ssr:
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
995
|
-
|
|
996
|
-
|
|
1022
|
+
ssr: apiOnly
|
|
1023
|
+
? []
|
|
1024
|
+
: [
|
|
1025
|
+
...new Set([
|
|
1026
|
+
...ssrExecutionPaths,
|
|
1027
|
+
...ssrPaths.filter(logicalPath => logicalPath !== 'index.js'),
|
|
1028
|
+
]),
|
|
1029
|
+
],
|
|
997
1030
|
uiClient: uiClientPaths.filter(logicalPath =>
|
|
998
1031
|
COMPILED_MODULE_PATTERN.test(logicalPath),
|
|
999
1032
|
),
|
|
@@ -1010,7 +1043,9 @@ const createNodeStagedReleaseArtifactInputs = async (
|
|
|
1010
1043
|
uiClient: [...uiClientPaths].sort((left, right) =>
|
|
1011
1044
|
left.localeCompare(right),
|
|
1012
1045
|
),
|
|
1013
|
-
ssr:
|
|
1046
|
+
ssr: apiOnly
|
|
1047
|
+
? []
|
|
1048
|
+
: [...ssrPaths].sort((left, right) => left.localeCompare(right)),
|
|
1014
1049
|
apiBackend: [...apiBackendPaths].sort((left, right) =>
|
|
1015
1050
|
left.localeCompare(right),
|
|
1016
1051
|
),
|
|
@@ -1036,6 +1071,7 @@ export const emitNodeStagedReleaseEnvelope = async ({
|
|
|
1036
1071
|
const { artifacts, surfaces } = await createNodeStagedReleaseArtifactInputs(
|
|
1037
1072
|
outputDirectory,
|
|
1038
1073
|
source.identity,
|
|
1074
|
+
source.surfaces.uiClient.length === 0,
|
|
1039
1075
|
);
|
|
1040
1076
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
1041
1077
|
artifactRoot: outputDirectory,
|
|
@@ -1077,6 +1113,7 @@ export const stageCloudflareReleaseEnvelope = async ({
|
|
|
1077
1113
|
const createCloudflareStagedReleaseArtifactInputs = async (
|
|
1078
1114
|
outputDirectory: string,
|
|
1079
1115
|
identity: MicroVerticalReleaseIdentity,
|
|
1116
|
+
apiOnly: boolean,
|
|
1080
1117
|
) => {
|
|
1081
1118
|
const files = await collectFiles(outputDirectory);
|
|
1082
1119
|
for (const requiredPath of [
|
|
@@ -1084,7 +1121,7 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1084
1121
|
'server/modern-worker-manifest.json',
|
|
1085
1122
|
'wrangler.json',
|
|
1086
1123
|
'package.json',
|
|
1087
|
-
'worker/package.json',
|
|
1124
|
+
...(apiOnly ? [] : ['worker/package.json']),
|
|
1088
1125
|
]) {
|
|
1089
1126
|
if (!files.includes(requiredPath)) {
|
|
1090
1127
|
throw new Error(
|
|
@@ -1105,28 +1142,46 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1105
1142
|
}
|
|
1106
1143
|
const backendManifestPath = `public/${BACKEND_FEDERATION_MANIFEST_FILE}`;
|
|
1107
1144
|
const backendContainerPath = `public/${BACKEND_FEDERATION_REMOTE_ENTRY_FILE}`;
|
|
1108
|
-
const apiBackendPaths = files.filter(
|
|
1109
|
-
|
|
1110
|
-
|
|
1145
|
+
const apiBackendPaths = files.filter(isEffectBffWorkerArtifact);
|
|
1146
|
+
if (!files.some(logicalPath => EFFECT_BFF_WORKER_PATTERN.test(logicalPath))) {
|
|
1147
|
+
throw new Error(
|
|
1148
|
+
'[ultramodern-release-envelope] final Cloudflare staging has no actual Effect API/BFF worker artifact.',
|
|
1149
|
+
);
|
|
1150
|
+
}
|
|
1111
1151
|
const ssrPaths = files.filter(
|
|
1112
1152
|
logicalPath =>
|
|
1113
1153
|
(logicalPath.startsWith('server/') ||
|
|
1114
1154
|
logicalPath.startsWith('worker/')) &&
|
|
1115
1155
|
COMPILED_MODULE_PATTERN.test(logicalPath) &&
|
|
1116
|
-
!
|
|
1156
|
+
!isEffectBffWorkerArtifact(logicalPath),
|
|
1117
1157
|
);
|
|
1118
1158
|
const uiClientPaths = files.filter(
|
|
1119
1159
|
logicalPath =>
|
|
1120
1160
|
logicalPath.startsWith('public/') &&
|
|
1161
|
+
logicalPath !== CRAWLER_POLICY_PATH &&
|
|
1162
|
+
!isPublicMetadataPath(logicalPath) &&
|
|
1163
|
+
logicalPath !== PUBLIC_BUILD_ARTIFACT_PATH &&
|
|
1121
1164
|
logicalPath !== backendManifestPath &&
|
|
1122
1165
|
logicalPath !== backendContainerPath,
|
|
1123
1166
|
);
|
|
1167
|
+
if (
|
|
1168
|
+
apiOnly &&
|
|
1169
|
+
(uiClientPaths.length > 0 ||
|
|
1170
|
+
ssrPaths.some(path => path !== 'server/index.mjs'))
|
|
1171
|
+
) {
|
|
1172
|
+
throw new Error(
|
|
1173
|
+
'[ultramodern-release-envelope] final API-only Cloudflare staging contains an undeclared UI/client or SSR surface.',
|
|
1174
|
+
);
|
|
1175
|
+
}
|
|
1124
1176
|
for (const [surface, paths] of [
|
|
1125
1177
|
['UI/client', uiClientPaths],
|
|
1126
|
-
['Cloudflare SSR', ssrPaths],
|
|
1178
|
+
['Cloudflare SSR', apiOnly ? [] : ssrPaths],
|
|
1127
1179
|
['Cloudflare API/backend', apiBackendPaths],
|
|
1128
1180
|
] as const) {
|
|
1129
|
-
if (
|
|
1181
|
+
if (
|
|
1182
|
+
paths.length === 0 &&
|
|
1183
|
+
(!apiOnly || surface === 'Cloudflare API/backend')
|
|
1184
|
+
) {
|
|
1130
1185
|
throw new Error(
|
|
1131
1186
|
`[ultramodern-release-envelope] final Cloudflare staging has no ${surface} artifacts.`,
|
|
1132
1187
|
);
|
|
@@ -1140,18 +1195,25 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1140
1195
|
}
|
|
1141
1196
|
}
|
|
1142
1197
|
|
|
1143
|
-
const clientExecutionPaths =
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1198
|
+
const clientExecutionPaths = apiOnly
|
|
1199
|
+
? []
|
|
1200
|
+
: await manifestReferencedClientModules(
|
|
1201
|
+
outputDirectory,
|
|
1202
|
+
files,
|
|
1203
|
+
'public/mf-manifest.json',
|
|
1204
|
+
);
|
|
1205
|
+
const ssrExecutionPaths = apiOnly
|
|
1206
|
+
? []
|
|
1207
|
+
: await routeReferencedSsrModules(
|
|
1208
|
+
outputDirectory,
|
|
1209
|
+
files,
|
|
1210
|
+
'cloudflare',
|
|
1211
|
+
'server/route.json',
|
|
1212
|
+
);
|
|
1213
|
+
if (
|
|
1214
|
+
!apiOnly &&
|
|
1215
|
+
(clientExecutionPaths.length === 0 || ssrExecutionPaths.length === 0)
|
|
1216
|
+
) {
|
|
1155
1217
|
throw new Error(
|
|
1156
1218
|
'[ultramodern-release-envelope] final Cloudflare manifests must reference emitted UI/client and SSR execution modules.',
|
|
1157
1219
|
);
|
|
@@ -1167,7 +1229,11 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1167
1229
|
}
|
|
1168
1230
|
};
|
|
1169
1231
|
add(uiClientPaths, 'browser');
|
|
1170
|
-
|
|
1232
|
+
if (files.includes(CRAWLER_POLICY_PATH)) {
|
|
1233
|
+
runtimeByPath.set(CRAWLER_POLICY_PATH, 'crawler-policy');
|
|
1234
|
+
}
|
|
1235
|
+
add(files.filter(isPublicMetadataPath), 'public-metadata');
|
|
1236
|
+
add(apiOnly ? [] : ssrPaths, 'workerd');
|
|
1171
1237
|
add(apiBackendPaths, 'workerd-effect');
|
|
1172
1238
|
add([backendManifestPath], 'module-federation-manifest');
|
|
1173
1239
|
add([backendContainerPath], 'commonjs-module');
|
|
@@ -1178,12 +1244,16 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1178
1244
|
{
|
|
1179
1245
|
apiBackend: apiBackendPaths,
|
|
1180
1246
|
backendFederation: [backendManifestPath, backendContainerPath],
|
|
1181
|
-
ssr:
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1247
|
+
ssr: apiOnly
|
|
1248
|
+
? []
|
|
1249
|
+
: [
|
|
1250
|
+
...new Set([
|
|
1251
|
+
...ssrExecutionPaths,
|
|
1252
|
+
...ssrPaths.filter(logicalPath =>
|
|
1253
|
+
logicalPath.startsWith('worker/'),
|
|
1254
|
+
),
|
|
1255
|
+
]),
|
|
1256
|
+
],
|
|
1187
1257
|
uiClient: uiClientPaths.filter(logicalPath =>
|
|
1188
1258
|
COMPILED_MODULE_PATTERN.test(logicalPath),
|
|
1189
1259
|
),
|
|
@@ -1200,7 +1270,9 @@ const createCloudflareStagedReleaseArtifactInputs = async (
|
|
|
1200
1270
|
uiClient: [...uiClientPaths].sort((left, right) =>
|
|
1201
1271
|
left.localeCompare(right),
|
|
1202
1272
|
),
|
|
1203
|
-
ssr:
|
|
1273
|
+
ssr: apiOnly
|
|
1274
|
+
? []
|
|
1275
|
+
: [...ssrPaths].sort((left, right) => left.localeCompare(right)),
|
|
1204
1276
|
apiBackend: [...apiBackendPaths].sort((left, right) =>
|
|
1205
1277
|
left.localeCompare(right),
|
|
1206
1278
|
),
|
|
@@ -1230,6 +1302,7 @@ export const emitCloudflareStagedReleaseEnvelope = async ({
|
|
|
1230
1302
|
await createCloudflareStagedReleaseArtifactInputs(
|
|
1231
1303
|
outputDirectory,
|
|
1232
1304
|
source.identity,
|
|
1305
|
+
source.surfaces.uiClient.length === 0,
|
|
1233
1306
|
);
|
|
1234
1307
|
const staged = await createMicroVerticalReleaseEnvelope({
|
|
1235
1308
|
artifactRoot: outputDirectory,
|
|
@@ -381,8 +381,8 @@ const assertUniqueSortedArtifacts = (
|
|
|
381
381
|
};
|
|
382
382
|
|
|
383
383
|
const assertSurfacePaths = (value: unknown, location: string): string[] => {
|
|
384
|
-
if (!Array.isArray(value)
|
|
385
|
-
throw new Error(`${location} must
|
|
384
|
+
if (!Array.isArray(value)) {
|
|
385
|
+
throw new Error(`${location} must be an array of artifact paths.`);
|
|
386
386
|
}
|
|
387
387
|
const paths = value.map((item, index) =>
|
|
388
388
|
assertNormalizedLogicalPath(item, `${location}[${index}]`),
|
|
@@ -413,10 +413,31 @@ const assertSurfaces = (value: unknown): MicroVerticalReleaseSurfaces => {
|
|
|
413
413
|
['manifest', 'container'],
|
|
414
414
|
'surfaces.backendFederation',
|
|
415
415
|
);
|
|
416
|
+
const uiClient = assertSurfacePaths(surfaces.uiClient, 'surfaces.uiClient');
|
|
417
|
+
const ssr = assertSurfacePaths(surfaces.ssr, 'surfaces.ssr');
|
|
418
|
+
const apiBackend = assertSurfacePaths(
|
|
419
|
+
surfaces.apiBackend,
|
|
420
|
+
'surfaces.apiBackend',
|
|
421
|
+
);
|
|
422
|
+
if (uiClient.length > 0 && ssr.length === 0) {
|
|
423
|
+
throw new Error(
|
|
424
|
+
'surfaces.ssr must contain at least one artifact path when UI/client is declared.',
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
if (ssr.length > 0 && uiClient.length === 0) {
|
|
428
|
+
throw new Error(
|
|
429
|
+
'surfaces.uiClient must contain at least one artifact path when SSR is declared.',
|
|
430
|
+
);
|
|
431
|
+
}
|
|
432
|
+
if (apiBackend.length === 0) {
|
|
433
|
+
throw new Error(
|
|
434
|
+
'surfaces.apiBackend must contain at least one artifact path.',
|
|
435
|
+
);
|
|
436
|
+
}
|
|
416
437
|
return {
|
|
417
|
-
uiClient
|
|
418
|
-
ssr
|
|
419
|
-
apiBackend
|
|
438
|
+
uiClient,
|
|
439
|
+
ssr,
|
|
440
|
+
apiBackend,
|
|
420
441
|
backendFederation: {
|
|
421
442
|
manifest: assertNormalizedLogicalPath(
|
|
422
443
|
backendFederation.manifest,
|