@bleedingdev/modern-js-create 3.5.0-ultramodern.27 → 3.5.0-ultramodern.29

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/dist/cjs/ultramodern-tooling/commands.cjs +497 -155
  2. package/dist/cjs/ultramodern-tooling/config.cjs +149 -8
  3. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +25 -0
  4. package/dist/cjs/ultramodern-workspace/api.cjs +44 -0
  5. package/dist/cjs/ultramodern-workspace/backend-federation.cjs +282 -0
  6. package/dist/cjs/ultramodern-workspace/contracts.cjs +24 -8
  7. package/dist/cjs/ultramodern-workspace/delivery-unit-sync.cjs +157 -0
  8. package/dist/cjs/ultramodern-workspace/delivery-unit.cjs +88 -0
  9. package/dist/cjs/ultramodern-workspace/descriptors.cjs +24 -0
  10. package/dist/cjs/ultramodern-workspace/module-federation.cjs +122 -44
  11. package/dist/cjs/ultramodern-workspace/package-json.cjs +11 -34
  12. package/dist/cjs/ultramodern-workspace/pnpm-workspace-policy-plan.cjs +166 -0
  13. package/dist/cjs/ultramodern-workspace/tooling-command-catalog.cjs +153 -0
  14. package/dist/cjs/ultramodern-workspace/workspace-script-plan.cjs +175 -0
  15. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +43 -62
  16. package/dist/cjs/ultramodern-workspace/workspace-validation-contract.cjs +97 -0
  17. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +9 -3
  18. package/dist/cjs/ultramodern-workspace/zerops.cjs +100 -0
  19. package/dist/esm/ultramodern-tooling/commands.js +500 -158
  20. package/dist/esm/ultramodern-tooling/config.js +144 -9
  21. package/dist/esm/ultramodern-workspace/add-vertical.js +26 -1
  22. package/dist/esm/ultramodern-workspace/api.js +43 -2
  23. package/dist/esm/ultramodern-workspace/backend-federation.js +217 -0
  24. package/dist/esm/ultramodern-workspace/contracts.js +24 -8
  25. package/dist/esm/ultramodern-workspace/delivery-unit-sync.js +108 -0
  26. package/dist/esm/ultramodern-workspace/delivery-unit.js +31 -0
  27. package/dist/esm/ultramodern-workspace/descriptors.js +13 -1
  28. package/dist/esm/ultramodern-workspace/module-federation.js +121 -36
  29. package/dist/esm/ultramodern-workspace/package-json.js +11 -31
  30. package/dist/esm/ultramodern-workspace/pnpm-workspace-policy-plan.js +113 -0
  31. package/dist/esm/ultramodern-workspace/tooling-command-catalog.js +100 -0
  32. package/dist/esm/ultramodern-workspace/workspace-script-plan.js +110 -0
  33. package/dist/esm/ultramodern-workspace/workspace-scripts.js +35 -63
  34. package/dist/esm/ultramodern-workspace/workspace-validation-contract.js +59 -0
  35. package/dist/esm/ultramodern-workspace/write-workspace.js +11 -5
  36. package/dist/esm/ultramodern-workspace/zerops.js +62 -0
  37. package/dist/esm-node/ultramodern-tooling/commands.js +500 -158
  38. package/dist/esm-node/ultramodern-tooling/config.js +144 -9
  39. package/dist/esm-node/ultramodern-workspace/add-vertical.js +26 -1
  40. package/dist/esm-node/ultramodern-workspace/api.js +43 -2
  41. package/dist/esm-node/ultramodern-workspace/backend-federation.js +218 -0
  42. package/dist/esm-node/ultramodern-workspace/contracts.js +24 -8
  43. package/dist/esm-node/ultramodern-workspace/delivery-unit-sync.js +109 -0
  44. package/dist/esm-node/ultramodern-workspace/delivery-unit.js +32 -0
  45. package/dist/esm-node/ultramodern-workspace/descriptors.js +13 -1
  46. package/dist/esm-node/ultramodern-workspace/module-federation.js +121 -36
  47. package/dist/esm-node/ultramodern-workspace/package-json.js +11 -31
  48. package/dist/esm-node/ultramodern-workspace/pnpm-workspace-policy-plan.js +114 -0
  49. package/dist/esm-node/ultramodern-workspace/tooling-command-catalog.js +101 -0
  50. package/dist/esm-node/ultramodern-workspace/workspace-script-plan.js +111 -0
  51. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +35 -63
  52. package/dist/esm-node/ultramodern-workspace/workspace-validation-contract.js +60 -0
  53. package/dist/esm-node/ultramodern-workspace/write-workspace.js +11 -5
  54. package/dist/esm-node/ultramodern-workspace/zerops.js +63 -0
  55. package/dist/types/ultramodern-tooling/config.d.ts +6 -0
  56. package/dist/types/ultramodern-workspace/api.d.ts +1 -0
  57. package/dist/types/ultramodern-workspace/backend-federation.d.ts +11 -0
  58. package/dist/types/ultramodern-workspace/contracts.d.ts +1 -1
  59. package/dist/types/ultramodern-workspace/delivery-unit-sync.d.ts +6 -0
  60. package/dist/types/ultramodern-workspace/delivery-unit.d.ts +27 -0
  61. package/dist/types/ultramodern-workspace/descriptors.d.ts +4 -0
  62. package/dist/types/ultramodern-workspace/module-federation.d.ts +4 -9
  63. package/dist/types/ultramodern-workspace/package-json.d.ts +0 -1
  64. package/dist/types/ultramodern-workspace/pnpm-workspace-policy-plan.d.ts +39 -0
  65. package/dist/types/ultramodern-workspace/tooling-command-catalog.d.ts +18 -0
  66. package/dist/types/ultramodern-workspace/workspace-script-plan.d.ts +72 -0
  67. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +3 -0
  68. package/dist/types/ultramodern-workspace/workspace-validation-contract.d.ts +42 -0
  69. package/dist/types/ultramodern-workspace/zerops.d.ts +2 -0
  70. package/package.json +3 -3
  71. package/template-workspace/.gitignore.handlebars +1 -0
  72. package/template-workspace/scripts/bootstrap-agent-skills.mjs +11 -3
  73. package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +40 -2
  74. package/templates/workspace-scripts/generate-node-backend-federation.mjs +270 -0
  75. package/templates/workspace-scripts/materialize-zerops-runtime.mjs +448 -0
  76. package/templates/workspace-scripts/proof-cloudflare-version.mjs +234 -8
  77. package/templates/workspace-scripts/proof-node-backend-federation.mjs +729 -0
  78. package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +188 -2
  79. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +322 -8
@@ -20,8 +20,8 @@ function resolveModuleFederationPublicPath(publicPath, manifestUrl) {
20
20
  }
21
21
  }
22
22
 
23
- async function fetchText(url) {
24
- const response = await fetch(url);
23
+ async function fetchText(url, init) {
24
+ const response = await fetch(url, init);
25
25
  return {
26
26
  ok: response.ok,
27
27
  status: response.status,
@@ -48,6 +48,52 @@ function parseMaybeJson(body) {
48
48
  }
49
49
  }
50
50
 
51
+ function jsonPathValue(value, path) {
52
+ const segments = String(path ?? '')
53
+ .split('.')
54
+ .filter(Boolean);
55
+ let current = value;
56
+
57
+ for (const segment of segments) {
58
+ if (current === null || current === undefined) {
59
+ return undefined;
60
+ }
61
+
62
+ if (Array.isArray(current) && /^\d+$/u.test(segment)) {
63
+ current = current[Number(segment)];
64
+ continue;
65
+ }
66
+
67
+ if (typeof current === 'object' && Object.hasOwn(current, segment)) {
68
+ current = current[segment];
69
+ continue;
70
+ }
71
+
72
+ return undefined;
73
+ }
74
+
75
+ return current;
76
+ }
77
+
78
+ function jsonValuesEqual(left, right) {
79
+ return JSON.stringify(left) === JSON.stringify(right);
80
+ }
81
+
82
+ function normalizeJsonSmokeExpectations(check) {
83
+ if (Array.isArray(check.expectations)) {
84
+ return check.expectations;
85
+ }
86
+
87
+ if (check.expect && typeof check.expect === 'object') {
88
+ return Object.entries(check.expect).map(([path, equals]) => ({
89
+ path,
90
+ equals,
91
+ }));
92
+ }
93
+
94
+ return [];
95
+ }
96
+
51
97
  function markerFromJson(value) {
52
98
  if (!value || typeof value !== 'object') {
53
99
  return undefined;
@@ -687,6 +733,27 @@ function validateUiMarkerEvidence(evidence, app, ssr) {
687
733
  status: uiMarker === app.marker?.build ? 'pass' : 'fail',
688
734
  });
689
735
  assert(uiMarker === app.marker?.build, `${app.id} UI marker mismatch`);
736
+
737
+ if (app.deliveryUnit) {
738
+ evidence.assertions.push({
739
+ type: 'delivery-unit-ui-marker',
740
+ unitId: app.deliveryUnit.unitId,
741
+ expected: app.deliveryUnit.surfaces?.ui?.buildMarker ?? app.deliveryUnit.buildMarker,
742
+ actual: uiMarker,
743
+ status:
744
+ uiMarker === (app.deliveryUnit.surfaces?.ui?.buildMarker ?? app.deliveryUnit.buildMarker)
745
+ ? 'pass'
746
+ : 'fail',
747
+ });
748
+ assert(
749
+ uiMarker !== undefined,
750
+ `${app.id} delivery unit ${app.deliveryUnit.unitId} is declared but SSR response is missing its build marker`,
751
+ );
752
+ assert(
753
+ uiMarker === (app.deliveryUnit.surfaces?.ui?.buildMarker ?? app.deliveryUnit.buildMarker),
754
+ `${app.id} delivery unit ${app.deliveryUnit.unitId} UI surface build marker mismatch`,
755
+ );
756
+ }
690
757
  }
691
758
 
692
759
  function validateCssRootMarkerEvidence(evidence, app, ssr) {
@@ -857,6 +924,123 @@ async function validateReadinessEvidence(evidence, app, publicUrl, routes) {
857
924
  `${app.id} Effect readiness returned HTTP ${readiness.status}`,
858
925
  );
859
926
  assert(apiMarker === app.marker?.build, `${app.id} API marker mismatch`);
927
+
928
+ if (app.deliveryUnit) {
929
+ evidence.assertions.push({
930
+ type: 'delivery-unit-api-marker',
931
+ route: routes.apiReadiness,
932
+ unitId: app.deliveryUnit.unitId,
933
+ expected: app.deliveryUnit.buildMarker,
934
+ actual: apiMarker,
935
+ status: apiMarker === app.deliveryUnit.buildMarker ? 'pass' : 'fail',
936
+ });
937
+ assert(
938
+ apiMarker !== undefined,
939
+ `${app.id} delivery unit ${app.deliveryUnit.unitId} is declared but readiness response is missing its build marker`,
940
+ );
941
+ assert(
942
+ apiMarker === app.deliveryUnit.buildMarker,
943
+ `${app.id} delivery unit ${app.deliveryUnit.unitId} API surface build marker mismatch`,
944
+ );
945
+ }
946
+ }
947
+ }
948
+
949
+ async function validateServiceBindingEvidence(evidence, app, publicUrl) {
950
+ const serviceBindings = app.deploy?.cloudflare?.serviceBindings ?? [];
951
+
952
+ for (const binding of serviceBindings) {
953
+ const response = await fetchText(joinUrl(publicUrl, binding.route));
954
+ const responseJson = parseMaybeJson(response.body);
955
+ const apiMarker = markerFromJson(responseJson);
956
+ const passed = response.ok && apiMarker === binding.expectedMarker;
957
+
958
+ evidence.assertions.push({
959
+ type: 'service-binding-api-marker',
960
+ appId: binding.appId,
961
+ binding: binding.binding,
962
+ route: binding.route,
963
+ expected: binding.expectedMarker,
964
+ actual: apiMarker,
965
+ status: passed ? 'pass' : 'fail',
966
+ statusCode: response.status,
967
+ });
968
+ assert(
969
+ response.ok,
970
+ `${app.id} service binding ${binding.binding} returned HTTP ${response.status}`,
971
+ );
972
+ assert(
973
+ apiMarker === binding.expectedMarker,
974
+ `${app.id} service binding ${binding.binding} API marker mismatch`,
975
+ );
976
+ }
977
+ }
978
+
979
+ async function validateJsonSmokeEvidence(evidence, app, publicUrl) {
980
+ const checks = app.deploy?.cloudflare?.jsonSmokeChecks ?? [];
981
+
982
+ for (const check of checks) {
983
+ const route = check.route;
984
+ assert(
985
+ typeof route === 'string' && route.length > 0,
986
+ `${app.id} JSON smoke check is missing a route`,
987
+ );
988
+
989
+ const method = String(check.method ?? 'GET').toUpperCase();
990
+ const requestBody =
991
+ check.body ?? check.payload ?? check.requestBody ?? undefined;
992
+ const response = await fetchText(joinUrl(publicUrl, route), {
993
+ method,
994
+ headers: {
995
+ accept: 'application/json',
996
+ ...(requestBody === undefined
997
+ ? {}
998
+ : { 'content-type': 'application/json' }),
999
+ },
1000
+ ...(requestBody === undefined
1001
+ ? {}
1002
+ : { body: JSON.stringify(requestBody) }),
1003
+ });
1004
+ const responseJson = parseMaybeJson(response.body);
1005
+ const checkId = check.id ?? route;
1006
+
1007
+ evidence.assertions.push({
1008
+ type: 'json-smoke-http',
1009
+ id: checkId,
1010
+ method,
1011
+ route,
1012
+ status: response.ok && responseJson !== undefined ? 'pass' : 'fail',
1013
+ statusCode: response.status,
1014
+ });
1015
+ assert(
1016
+ response.ok,
1017
+ `${app.id} JSON smoke ${checkId} returned HTTP ${response.status}`,
1018
+ );
1019
+ assert(
1020
+ responseJson !== undefined,
1021
+ `${app.id} JSON smoke ${checkId} did not return JSON`,
1022
+ );
1023
+
1024
+ for (const expectation of normalizeJsonSmokeExpectations(check)) {
1025
+ const actual = jsonPathValue(responseJson, expectation.path);
1026
+ const passed = jsonValuesEqual(actual, expectation.equals);
1027
+
1028
+ evidence.assertions.push({
1029
+ type: 'json-smoke-value',
1030
+ id: checkId,
1031
+ route,
1032
+ path: expectation.path,
1033
+ expected: expectation.equals,
1034
+ actual,
1035
+ status: passed ? 'pass' : 'fail',
1036
+ });
1037
+ assert(
1038
+ passed,
1039
+ `${app.id} JSON smoke ${checkId} expected ${expectation.path} to equal ${JSON.stringify(
1040
+ expectation.equals,
1041
+ )}`,
1042
+ );
1043
+ }
860
1044
  }
861
1045
  }
862
1046
 
@@ -869,6 +1053,8 @@ async function validateApp(app, publicUrl) {
869
1053
  await validateModuleFederationManifestEvidence(evidence, app, publicUrl, routes);
870
1054
  await validateI18nEvidence(evidence, app, publicUrl, routes);
871
1055
  await validateReadinessEvidence(evidence, app, publicUrl, routes);
1056
+ await validateServiceBindingEvidence(evidence, app, publicUrl);
1057
+ await validateJsonSmokeEvidence(evidence, app, publicUrl);
872
1058
 
873
1059
  return evidence;
874
1060
  }
@@ -53,6 +53,14 @@ const assertExists = relativePath => {
53
53
  const assertNotExists = relativePath => {
54
54
  assert(!fs.existsSync(path.join(root, relativePath)), `Unexpected ${relativePath}`);
55
55
  };
56
+ const assertAnyOf = relativePaths => {
57
+ assert(
58
+ relativePaths.some(relativePath =>
59
+ fs.existsSync(path.join(root, relativePath)),
60
+ ),
61
+ `Missing one of: ${relativePaths.join(', ')}`,
62
+ );
63
+ };
56
64
  const formatJson = value =>
57
65
  value === undefined ? 'undefined' : JSON.stringify(value);
58
66
  const selfCheckFailure = (contract, message, fixArea) =>
@@ -136,6 +144,28 @@ const buildMarkerFor = app =>
136
144
  .update(`${packageScope}:${app.packageSuffix}:${app.id}:0.1.0`)
137
145
  .digest('hex')
138
146
  .slice(0, 16);
147
+ const deliveryUnitIdentityFixArea =
148
+ 'regenerate vertical identity from delivery-unit record; do not hand-edit surface markers';
149
+ const deliveryUnitBlock = record => ({
150
+ schemaVersion: record?.schemaVersion,
151
+ kind: record?.kind,
152
+ unitId: record?.unitId,
153
+ packageName: record?.packageName,
154
+ version: record?.version,
155
+ buildMarker: record?.buildMarker,
156
+ sourceRevision: record?.sourceRevision,
157
+ });
158
+ const assertDeliveryUnitMarkerSpread = (source, exportName, contract) => {
159
+ const spreadPattern = new RegExp(
160
+ `export const ${exportName} = \\{\\s*\\.\\.\\.ultramodernDeliveryUnit,`,
161
+ );
162
+ assertSelfCheck(
163
+ spreadPattern.test(source),
164
+ contract,
165
+ `${exportName} must spread ...ultramodernDeliveryUnit rather than hand-forking per-surface fields`,
166
+ deliveryUnitIdentityFixArea,
167
+ );
168
+ };
139
169
  const normalizeCompactApp = rawApp => {
140
170
  const id = String(rawApp.id);
141
171
  const kind = rawApp.kind === 'vertical' ? 'vertical' : 'shell';
@@ -772,6 +802,107 @@ const expectedManifestUrl = vertical =>
772
802
  `http://localhost:${vertical.port}/mf-manifest.json`;
773
803
  const expectedApiUrl = vertical =>
774
804
  `http://localhost:${vertical.port}${vertical.apiPrefix}`;
805
+ const expectedBackendFederationName = vertical => `${vertical.mfName}Backend`;
806
+ const expectedBackendManifestUrl = vertical =>
807
+ `http://localhost:${vertical.port}/backend-mf-manifest.json`;
808
+ const expectedBackendContainerEntry = vertical =>
809
+ `http://localhost:${vertical.port}/backendRemoteEntry.mjs`;
810
+ const expectedBackendManifestEnv = vertical =>
811
+ `VERTICAL_${toEnvSegment(vertical.domain ?? vertical.id)}_BACKEND_MF_MANIFEST`;
812
+ const expectedPublicUrlEnv = vertical =>
813
+ `ULTRAMODERN_PUBLIC_URL_${toEnvSegment(vertical.id)}`;
814
+ const expectedCloudflareWorkerName = vertical =>
815
+ toKebabCase(`${packageScope}-${vertical.packageSuffix ?? vertical.id}`).slice(
816
+ 0,
817
+ 63,
818
+ );
819
+ const backendFederationSubset = backendFederation => ({
820
+ role: backendFederation?.role,
821
+ name: backendFederation?.name,
822
+ runtimeFramework: backendFederation?.runtimeFramework,
823
+ strictEffectApproach: backendFederation?.strictEffectApproach,
824
+ exposeRuntime: backendFederation?.exposes?.['./effect-api']?.runtime,
825
+ exposeReadiness: backendFederation?.exposes?.['./effect-api']?.readiness,
826
+ versionBoundary: {
827
+ invariant: backendFederation?.versionBoundary?.invariant,
828
+ uiManifestUrl: backendFederation?.versionBoundary?.ui?.manifestUrl,
829
+ apiReadiness: backendFederation?.versionBoundary?.api?.readiness,
830
+ },
831
+ cloudflare: {
832
+ kind: backendFederation?.executionSurfaces?.cloudflare?.kind,
833
+ workerName: backendFederation?.executionSurfaces?.cloudflare?.workerName,
834
+ publicUrlEnv:
835
+ backendFederation?.executionSurfaces?.cloudflare?.publicUrlEnv,
836
+ zephyrRuntime:
837
+ backendFederation?.executionSurfaces?.cloudflare?.zephyr?.runtime,
838
+ },
839
+ node: {
840
+ kind: backendFederation?.executionSurfaces?.node?.kind,
841
+ remoteName: backendFederation?.executionSurfaces?.node?.remoteName,
842
+ manifestEnv: backendFederation?.executionSurfaces?.node?.manifestEnv,
843
+ manifestUrl: backendFederation?.executionSurfaces?.node?.manifestUrl,
844
+ containerEntry:
845
+ backendFederation?.executionSurfaces?.node?.containerEntry,
846
+ remoteType: backendFederation?.executionSurfaces?.node?.remoteType,
847
+ expose: backendFederation?.executionSurfaces?.node?.expose,
848
+ },
849
+ compatibility: {
850
+ contractVersion: backendFederation?.compatibility?.contractVersion,
851
+ },
852
+ topLevelManifestUrl: backendFederation?.manifestUrl,
853
+ topLevelContainerEntry: backendFederation?.containerEntry,
854
+ });
855
+ const expectedBackendFederationSubset = vertical => ({
856
+ role: 'microvertical-server',
857
+ name: expectedBackendFederationName(vertical),
858
+ runtimeFramework: 'effect',
859
+ strictEffectApproach: true,
860
+ exposeRuntime: `${vertical.path}/api/index.ts`,
861
+ exposeReadiness: `${vertical.apiPrefix}/${vertical.stem}/readiness`,
862
+ versionBoundary: {
863
+ invariant: 'web-and-api-same-build',
864
+ uiManifestUrl: expectedManifestUrl(vertical),
865
+ apiReadiness: `${vertical.apiPrefix}/${vertical.stem}/readiness`,
866
+ },
867
+ cloudflare: {
868
+ kind: 'cloudflare-worker-snapshot',
869
+ workerName: expectedCloudflareWorkerName(vertical),
870
+ publicUrlEnv: expectedPublicUrlEnv(vertical),
871
+ zephyrRuntime: 'ssr-worker',
872
+ },
873
+ node: {
874
+ kind: 'node-mf-runtime',
875
+ remoteName: expectedBackendFederationName(vertical),
876
+ manifestEnv: expectedBackendManifestEnv(vertical),
877
+ manifestUrl: expectedBackendManifestUrl(vertical),
878
+ containerEntry: expectedBackendContainerEntry(vertical),
879
+ remoteType: 'module',
880
+ expose: './effect-api',
881
+ },
882
+ compatibility: {
883
+ contractVersion: 'microvertical-server-effect-v1',
884
+ },
885
+ topLevelManifestUrl: undefined,
886
+ topLevelContainerEntry: undefined,
887
+ });
888
+ const serverExecutionSubset = serverExecution => ({
889
+ apiBaseUrl: serverExecution?.apiBaseUrl,
890
+ versionBoundary: serverExecution?.versionBoundary,
891
+ cloudflareKind: serverExecution?.cloudflare?.kind,
892
+ cloudflareWorkerName: serverExecution?.cloudflare?.workerName,
893
+ nodeKind: serverExecution?.node?.kind,
894
+ nodeManifestUrl: serverExecution?.node?.manifestUrl,
895
+ nodeContainerEntry: serverExecution?.node?.containerEntry,
896
+ });
897
+ const expectedServerExecutionSubset = vertical => ({
898
+ apiBaseUrl: expectedApiUrl(vertical),
899
+ versionBoundary: 'web-and-api-same-build',
900
+ cloudflareKind: 'cloudflare-worker-snapshot',
901
+ cloudflareWorkerName: expectedCloudflareWorkerName(vertical),
902
+ nodeKind: 'node-mf-runtime',
903
+ nodeManifestUrl: expectedBackendManifestUrl(vertical),
904
+ nodeContainerEntry: expectedBackendContainerEntry(vertical),
905
+ });
775
906
  const remoteContractSubset = remote => ({
776
907
  id: remote?.id,
777
908
  name: remote?.name,
@@ -791,9 +922,11 @@ const requiredMicroVerticalPaths = vertical => [
791
922
  `${vertical.path}/package.json`,
792
923
  `${vertical.path}/tsconfig.json`,
793
924
  `${vertical.path}/tsconfig.mf-types.json`,
794
- `${vertical.path}/modern.config.ts`,
795
- `${vertical.path}/module-federation.config.ts`,
796
- `${vertical.path}/api/index.ts`,
925
+ `${vertical.path}/modern.config.ts`,
926
+ `${vertical.path}/module-federation.config.ts`,
927
+ `${vertical.path}/backend-federation.config.ts`,
928
+ `${vertical.path}/api/effect-api.ts`,
929
+ `${vertical.path}/api/index.ts`,
797
930
  `${vertical.path}/shared/api.ts`,
798
931
  `${vertical.path}/src/api/${vertical.stem}-client.ts`,
799
932
  `${vertical.path}/src/modern-app-env.d.ts`,
@@ -988,6 +1121,34 @@ const assertMicroVerticalContractGraph = ({
988
1121
  `topology/reference-topology.json verticals.${vertical.id}`,
989
1122
  'restore generated topology vertical entries',
990
1123
  );
1124
+ assertSameJson(
1125
+ backendFederationSubset(topologyEntry.backendFederation),
1126
+ expectedBackendFederationSubset(vertical),
1127
+ `topology/reference-topology.json verticals.${vertical.id}.backendFederation`,
1128
+ 'restore generated MicroVertical server execution contract',
1129
+ );
1130
+
1131
+ if (vertical.deliveryUnit) {
1132
+ const expectedDeliveryUnit = deliveryUnitBlock(vertical.deliveryUnit);
1133
+ assertSameJson(
1134
+ deliveryUnitBlock(topologyEntry.deliveryUnit),
1135
+ expectedDeliveryUnit,
1136
+ `topology/reference-topology.json verticals.${vertical.id}.deliveryUnit`,
1137
+ deliveryUnitIdentityFixArea,
1138
+ );
1139
+ assertSameJson(
1140
+ deliveryUnitBlock(topologyEntry.backendFederation?.deliveryUnit),
1141
+ expectedDeliveryUnit,
1142
+ `topology/reference-topology.json verticals.${vertical.id}.backendFederation.deliveryUnit`,
1143
+ deliveryUnitIdentityFixArea,
1144
+ );
1145
+ assertSelfCheck(
1146
+ topologyEntry.backendFederation?.versionBoundary?.identityRoot === 'deliveryUnit',
1147
+ `topology/reference-topology.json verticals.${vertical.id}.backendFederation.versionBoundary.identityRoot`,
1148
+ `Expected "deliveryUnit", found ${formatJson(topologyEntry.backendFederation?.versionBoundary?.identityRoot)}`,
1149
+ deliveryUnitIdentityFixArea,
1150
+ );
1151
+ }
991
1152
 
992
1153
  assertObject(
993
1154
  ownershipEntry,
@@ -1025,6 +1186,13 @@ const assertMicroVerticalContractGraph = ({
1025
1186
  `topology/local-overlays/development.json apis.${vertical.id}`,
1026
1187
  'restore generated local API overlay',
1027
1188
  );
1189
+ assertSameJson(
1190
+ serverExecutionSubset(overlay.serverExecution?.[vertical.id]),
1191
+ expectedServerExecutionSubset(vertical),
1192
+ `topology/local-overlays/development.json serverExecution.${vertical.id}`,
1193
+ 'restore generated local MicroVertical server execution overlay',
1194
+ );
1195
+
1028
1196
 
1029
1197
  assertSameJson(
1030
1198
  shellPackage.dependencies?.[vertical.packageName],
@@ -1230,7 +1398,7 @@ const packageJsonFiles = startDir => {
1230
1398
  const current = queue.shift();
1231
1399
  for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
1232
1400
  if (
1233
- ['.git', '.output', 'dist', 'node_modules', 'repos'].includes(
1401
+ ['.git', '.output', '.zerops', 'dist', 'node_modules', 'repos'].includes(
1234
1402
  entry.name,
1235
1403
  )
1236
1404
  ) {
@@ -1513,8 +1681,6 @@ const requiredPaths = [
1513
1681
  '.github/renovate.json',
1514
1682
  '.github/workflows/ultramodern-workspace-gates.yml',
1515
1683
  '.agents/agent-reference-repos.json',
1516
- '.codex/skills-lock.json',
1517
- '.codex/rstackjs-agent-skills-LICENSE',
1518
1684
  'topology/reference-topology.json',
1519
1685
  'topology/ownership.json',
1520
1686
  'topology/local-overlays/development.json',
@@ -1522,13 +1688,17 @@ const requiredPaths = [
1522
1688
  'scripts/bootstrap-agent-skills.mts',
1523
1689
  'scripts/check-ultramodern-api-boundaries.mts',
1524
1690
  'scripts/check-ultramodern-i18n-boundaries.mts',
1691
+ 'scripts/generate-node-backend-federation.mts',
1525
1692
  'scripts/generate-public-surface-assets.mts',
1693
+ 'scripts/generate-tanstack-routes.mts',
1526
1694
  'scripts/proof-cloudflare-version.mts',
1695
+ 'scripts/proof-node-backend-federation.mts',
1527
1696
  'scripts/setup-agent-reference-repos.mts',
1528
1697
  'scripts/ultramodern-performance-readiness.config.mjs',
1529
1698
  'scripts/ultramodern-performance-readiness.mts',
1530
1699
  'scripts/ultramodern-typecheck.mts',
1531
1700
  'scripts/validate-ultramodern-workspace.mts',
1701
+ 'scripts/verify-cloudflare-output.mts',
1532
1702
  'apps/shell-super-app/package.json',
1533
1703
  'apps/shell-super-app/tsconfig.json',
1534
1704
  'apps/shell-super-app/tsconfig.mf-types.json',
@@ -1581,6 +1751,13 @@ for (const vertical of fullStackVerticals) {
1581
1751
  for (const requiredPath of requiredPaths) {
1582
1752
  assertExists(requiredPath);
1583
1753
  }
1754
+ // Agent skills metadata may live under .agents/ (agents-standard layout) or
1755
+ // .codex/ (legacy scaffold default).
1756
+ assertAnyOf(['.agents/skills-lock.json', '.codex/skills-lock.json']);
1757
+ assertAnyOf([
1758
+ '.agents/rstackjs-agent-skills-LICENSE',
1759
+ '.codex/rstackjs-agent-skills-LICENSE',
1760
+ ]);
1584
1761
  const pnpmWorkspace = readText('pnpm-workspace.yaml');
1585
1762
  assert(
1586
1763
  pnpmWorkspace.includes("'@effect/opentelemetry': {{effectVersion}}"),
@@ -1676,6 +1853,11 @@ assert(
1676
1853
  expectedModernPackageSpecifier('@modern-js/code-tools'),
1677
1854
  'Root must depend on @modern-js/code-tools through package source metadata',
1678
1855
  );
1856
+ assert(
1857
+ rootPackage.devDependencies?.['@modern-js/plugin-bff'] ===
1858
+ expectedModernPackageSpecifier('@modern-js/plugin-bff'),
1859
+ 'Root must depend on @modern-js/plugin-bff for Node backend federation proof',
1860
+ );
1679
1861
  if (packageSource.strategy === 'install') {
1680
1862
  const installSpecifier = packageSource.modernPackages?.specifier;
1681
1863
  assert(
@@ -1735,21 +1917,60 @@ if (bridgeConfig) {
1735
1917
  }
1736
1918
  assert(rootPackage.scripts?.['bridge:check'], 'Bridge workspaces must expose bridge:check');
1737
1919
  } else {
1738
- assert(rootPackage.scripts?.typecheck === 'node ./scripts/ultramodern-typecheck.mts --build tsconfig.json', 'Root typecheck must run TS-Go build mode across project references');
1920
+ assert(rootPackage.scripts?.typecheck === 'node ./scripts/ultramodern-typecheck.mts --project tsconfig.json', 'Root typecheck must run TS-Go across the root project reference graph');
1739
1921
  }
1740
1922
  assert(rootPackage.scripts?.['contract:check'] === 'node ./scripts/validate-ultramodern-workspace.mts', 'Root must expose contract:check');
1741
1923
  assert(rootPackage.scripts?.['api:check'] === 'node ./scripts/check-ultramodern-api-boundaries.mts', 'Root must expose api:check');
1742
1924
  assert(rootPackage.scripts?.['i18n:boundaries'] === 'node ./scripts/check-ultramodern-i18n-boundaries.mts', 'Root must expose i18n:boundaries');
1743
1925
  assert(rootPackage.scripts?.['performance:readiness'] === 'node ./scripts/ultramodern-performance-readiness.mts', 'Root must expose default-on performance readiness diagnostics');
1926
+ assert(rootPackage.scripts?.['node:backend-federation:generate'] === 'node ./scripts/generate-node-backend-federation.mts', 'Root must expose local Node backend federation artifact generation');
1927
+ assert(rootPackage.scripts?.['zerops:materialize'] === 'node ./scripts/materialize-zerops-runtime.mjs', 'Root must expose Zerops runtime materialization script');
1928
+ assertNotExists('scripts/generate-node-backend-federation.mjs');
1929
+ assertNotExists('scripts/proof-node-backend-federation.mjs');
1930
+ assertNotExists('scripts/verify-cloudflare-output.mjs');
1931
+ assertNotExists('scripts/generate-tanstack-routes.mjs');
1932
+ assert(
1933
+ rootPackage.scripts?.['node:proof'] ===
1934
+ 'node ./scripts/proof-node-backend-federation.mts',
1935
+ 'Root must expose node:proof for backend federation Effect modules',
1936
+ );
1744
1937
  assert(
1745
1938
  bridgeConfig
1746
1939
  ? rootPackage.scripts?.check?.includes('pnpm api:check') &&
1940
+ rootPackage.scripts.check.includes('pnpm node:proof') &&
1747
1941
  rootPackage.scripts.check.endsWith('&& pnpm performance:readiness && pnpm bridge:check')
1748
1942
  : rootPackage.scripts?.check?.includes('pnpm api:check') &&
1943
+ rootPackage.scripts.check.includes('pnpm node:proof') &&
1749
1944
  rootPackage.scripts.check.endsWith('&& pnpm performance:readiness'),
1750
- 'Root check must run default-on performance readiness diagnostics and bridge gates when configured',
1945
+ 'Root check must run Node backend federation proof, default-on performance readiness diagnostics, and bridge gates when configured',
1946
+ );
1947
+ const zeropsYaml = readText('zerops.yaml');
1948
+ assert(zeropsYaml.includes('zerops:'), 'Zerops manifest must include zerops services');
1949
+ assert(zeropsYaml.includes('setup: shell-super-app'), 'Zerops manifest must include shell service');
1950
+ assert(zeropsYaml.includes('base: nodejs@26'), 'Zerops manifest must use generated Node runtime major');
1951
+ assert(zeropsYaml.includes('deployFiles:'), 'Zerops manifest must deploy package-pruned runtime directories');
1952
+ assert(
1953
+ zeropsYaml.includes('start: cd .zerops/runtime/shell-super-app && npm run serve'),
1954
+ 'Zerops shell service must start from materialized runtime package',
1955
+ );
1956
+ assert(
1957
+ zeropsYaml.includes('SHELL_SUPER_APP_PORT:') &&
1958
+ zeropsYaml.includes('ULTRAMODERN_ZEROPS_SERVICE:'),
1959
+ 'Zerops manifest must expose service identity and ports',
1751
1960
  );
1961
+ for (const vertical of fullStackVerticals) {
1962
+ assert(zeropsYaml.includes(`setup: ${vertical.id}`), `${vertical.id} must have a Zerops service`);
1963
+ assert(
1964
+ zeropsYaml.includes(`~/.local/bin/mise exec -- pnpm run zerops:materialize -- --app ${vertical.id} --package ${vertical.packageName} --package-dir ${vertical.path}`),
1965
+ `${vertical.id} Zerops service must materialize its runtime package`,
1966
+ );
1967
+ assert(
1968
+ zeropsYaml.includes(`path: ${vertical.apiPrefix}/${vertical.stem}/readiness`),
1969
+ `${vertical.id} BFF readiness path must use generated API prefix`,
1970
+ );
1971
+ }
1752
1972
  const performanceReadinessConfig = readText('scripts/ultramodern-performance-readiness.config.mjs');
1973
+ const zeropsMaterializer = readText('scripts/materialize-zerops-runtime.mjs');
1753
1974
  const assertToolWrapper = (scriptPath, command) => {
1754
1975
  const source = readText(scriptPath);
1755
1976
  assert(source.includes('modern-js-create'), `${scriptPath} must delegate to modern-js-create`);
@@ -1760,6 +1981,16 @@ const assertToolWrapper = (scriptPath, command) => {
1760
1981
  assert(performanceReadinessConfig.includes('UltramodernPerformanceReadinessDiagnosticsConfig'), 'Performance readiness config must carry the typed opt-out surface');
1761
1982
  assert(performanceReadinessConfig.includes('enabled: true'), 'Performance readiness diagnostics must be default-on');
1762
1983
  assert(performanceReadinessConfig.includes("failOn: 'framework-invariant'"), 'Performance readiness diagnostics must only fail framework invariants by default');
1984
+ assert(zeropsMaterializer.includes("MODERNJS_DEPLOY: 'node'"), 'Zerops materializer must use Modern.js Node deploy target');
1985
+ assert(/'deploy',\s*'--skip-build'/.test(zeropsMaterializer), 'Zerops materializer must pass --skip-build directly to Modern deploy');
1986
+ assert(zeropsMaterializer.includes('normalizeRuntimePackageDependencies'), 'Zerops materializer must normalize generated Modern package aliases before installing dependencies');
1987
+ assert(zeropsMaterializer.includes('officialPackageName'), 'Zerops materializer must add official Modern.js npm aliases for generated runtime imports');
1988
+ assert(zeropsMaterializer.includes('installRuntimeDependencies'), 'Zerops materializer must install dependencies outside the workspace copy runtime node_modules');
1989
+ assert(zeropsMaterializer.includes('copyWorkspacePackage'), 'Zerops materializer must preserve generated local workspace package dependencies');
1990
+ assert(zeropsMaterializer.includes('makeWorkspacePackageRuntimeSafe'), 'Zerops materializer must rewrite copied workspace packages to runtime-safe JavaScript exports');
1991
+ assert(zeropsMaterializer.includes("serve: runtimePackage.scripts?.serve ?? 'node index.js'"), 'Zerops materializer must preserve runtime serve script fallback');
1992
+ assert(zeropsMaterializer.includes("'install', '--omit=dev'"), 'Zerops materializer must omit dev dependencies from runtime installs');
1993
+ assert(zeropsMaterializer.includes("'--legacy-peer-deps'"), 'Zerops materializer must tolerate generated-workspace peer dependency ranges in npm runtime install');
1763
1994
  assertToolWrapper('scripts/validate-ultramodern-workspace.mts', 'validate');
1764
1995
  assertToolWrapper('scripts/ultramodern-performance-readiness.mts', 'performance-readiness');
1765
1996
  const i18nBoundaryScript = readText('scripts/check-ultramodern-i18n-boundaries.mts');
@@ -1779,8 +2010,12 @@ assert(rootPackage.scripts?.postinstall === "oxfmt . '!repos/**' && node ./scrip
1779
2010
  assert(rootPackage.scripts?.['agents:refs:install'] === 'node ./scripts/setup-agent-reference-repos.mts', 'Root must expose agents:refs:install as the explicit reference repo installer');
1780
2011
  const agentSkillsBootstrap = readText('scripts/bootstrap-agent-skills.mts');
1781
2012
  assertToolWrapper('scripts/assert-mf-types.mts', 'mf-types');
2013
+ assertToolWrapper('scripts/generate-node-backend-federation.mts', 'backend-federation-generate');
1782
2014
  assertToolWrapper('scripts/generate-public-surface-assets.mts', 'public-surface');
2015
+ assertToolWrapper('scripts/generate-tanstack-routes.mts', 'routes-generate');
2016
+ assertToolWrapper('scripts/proof-node-backend-federation.mts', 'backend-federation-proof');
1783
2017
  assertToolWrapper('scripts/proof-cloudflare-version.mts', 'cloudflare-proof');
2018
+ assertToolWrapper('scripts/verify-cloudflare-output.mts', 'cloudflare-output-verify');
1784
2019
  assertToolWrapper('scripts/migrate-strict-effect.mts', 'migrate-strict-effect');
1785
2020
  assertToolWrapper('scripts/bootstrap-agent-skills.mts', 'skills');
1786
2021
  assert(!agentSkillsBootstrap.includes("run('brew'") && !agentSkillsBootstrap.includes('runShell('), 'Agent skills bootstrap must never invoke system package managers');
@@ -1927,6 +2162,54 @@ for (const vertical of fullStackVerticals) {
1927
2162
  const modernAppEnv = readText(`${vertical.path}/src/modern-app-env.d.ts`);
1928
2163
  const routeHead = readText(`${vertical.path}/src/routes/ultramodern-route-head.tsx`);
1929
2164
  const routeMetadata = readText(`${vertical.path}/src/routes/ultramodern-route-metadata.ts`);
2165
+ const ultramodernBuildSource = readText(`${vertical.path}/shared/ultramodern-build.ts`);
2166
+ if (vertical.deliveryUnit) {
2167
+ const buildLabel = `${vertical.path}/shared/ultramodern-build.ts ultramodernDeliveryUnit`;
2168
+ assertSelfCheck(
2169
+ ultramodernBuildSource.includes('export const ultramodernDeliveryUnit'),
2170
+ buildLabel,
2171
+ 'Missing ultramodernDeliveryUnit export',
2172
+ deliveryUnitIdentityFixArea,
2173
+ );
2174
+ const buildMatch = /\bbuild:\s*'([^']+)'/u.exec(ultramodernBuildSource);
2175
+ const unitIdMatch = /\bunitId:\s*'([^']+)'/u.exec(ultramodernBuildSource);
2176
+ const packageNameMatch = /\bpackageName:\s*'([^']+)'/u.exec(ultramodernBuildSource);
2177
+ const versionMatch = /\bversion:\s*'([^']+)'/u.exec(ultramodernBuildSource);
2178
+ assertSelfCheck(
2179
+ buildMatch?.[1] === vertical.deliveryUnit.buildMarker,
2180
+ buildLabel,
2181
+ `Expected build "${vertical.deliveryUnit.buildMarker}", found ${formatJson(buildMatch?.[1])}`,
2182
+ deliveryUnitIdentityFixArea,
2183
+ );
2184
+ assertSelfCheck(
2185
+ unitIdMatch?.[1] === vertical.deliveryUnit.unitId,
2186
+ buildLabel,
2187
+ `Expected unitId "${vertical.deliveryUnit.unitId}", found ${formatJson(unitIdMatch?.[1])}`,
2188
+ deliveryUnitIdentityFixArea,
2189
+ );
2190
+ assertSelfCheck(
2191
+ packageNameMatch?.[1] === vertical.deliveryUnit.packageName,
2192
+ buildLabel,
2193
+ `Expected packageName "${vertical.deliveryUnit.packageName}", found ${formatJson(packageNameMatch?.[1])}`,
2194
+ deliveryUnitIdentityFixArea,
2195
+ );
2196
+ assertSelfCheck(
2197
+ versionMatch?.[1] === vertical.deliveryUnit.version,
2198
+ buildLabel,
2199
+ `Expected version "${vertical.deliveryUnit.version}", found ${formatJson(versionMatch?.[1])}`,
2200
+ deliveryUnitIdentityFixArea,
2201
+ );
2202
+ assertDeliveryUnitMarkerSpread(
2203
+ ultramodernBuildSource,
2204
+ 'ultramodernUiMarker',
2205
+ `${vertical.path}/shared/ultramodern-build.ts ultramodernUiMarker`,
2206
+ );
2207
+ assertDeliveryUnitMarkerSpread(
2208
+ ultramodernBuildSource,
2209
+ 'ultramodernApiMarker',
2210
+ `${vertical.path}/shared/ultramodern-build.ts ultramodernApiMarker`,
2211
+ );
2212
+ }
1930
2213
  assert(modernAppEnv.includes("import '@modern-js/app-tools/types';"), `${vertical.id} app env must import the framework-owned app ambient type bundle through module resolution`);
1931
2214
  assert(/declare global \{\s*const ULTRAMODERN_SITE_URL: string;\s*\}/u.test(modernAppEnv), `${vertical.id} app env must keep generated globals explicit after importing app ambient types`);
1932
2215
  assert(!modernAppEnv.includes("declare module '*.svg'"), `${vertical.id} app env must not redeclare framework-owned svg asset modules`);
@@ -2057,6 +2340,37 @@ for (const vertical of fullStackVerticals) {
2057
2340
  assert(topologyEntry?.api?.readiness?.endpoint === `/${vertical.stem}/readiness`, `${vertical.id} topology readiness endpoint is incorrect`);
2058
2341
  assert(Object.keys(topologyEntry?.api?.domainOperations ?? {}).length >= 3, `${vertical.id} topology domain operations are missing`);
2059
2342
 
2343
+ if (vertical.deliveryUnit) {
2344
+ const expectedDeliveryUnit = deliveryUnitBlock(vertical.deliveryUnit);
2345
+ const compactAppEntry = ultramodernConfig.topology?.apps?.find(
2346
+ entry => entry?.id === vertical.id,
2347
+ );
2348
+ assertSameJson(
2349
+ deliveryUnitBlock(compactAppEntry?.deliveryUnit),
2350
+ expectedDeliveryUnit,
2351
+ `${compactConfigPath} topology.apps.${vertical.id}.deliveryUnit`,
2352
+ deliveryUnitIdentityFixArea,
2353
+ );
2354
+ assertSameJson(
2355
+ deliveryUnitBlock(compactAppEntry?.backendFederation?.deliveryUnit),
2356
+ expectedDeliveryUnit,
2357
+ `${compactConfigPath} topology.apps.${vertical.id}.backendFederation.deliveryUnit`,
2358
+ deliveryUnitIdentityFixArea,
2359
+ );
2360
+ assertSelfCheck(
2361
+ compactAppEntry?.backendFederation?.versionBoundary?.identityRoot === 'deliveryUnit',
2362
+ `${compactConfigPath} topology.apps.${vertical.id}.backendFederation.versionBoundary.identityRoot`,
2363
+ `Expected "deliveryUnit", found ${formatJson(compactAppEntry?.backendFederation?.versionBoundary?.identityRoot)}`,
2364
+ deliveryUnitIdentityFixArea,
2365
+ );
2366
+ assertSameJson(
2367
+ deliveryUnitBlock(compactAppEntry?.deliveryUnit),
2368
+ deliveryUnitBlock(topologyEntry?.deliveryUnit),
2369
+ `${compactConfigPath} vs topology/reference-topology.json verticals.${vertical.id}.deliveryUnit`,
2370
+ deliveryUnitIdentityFixArea,
2371
+ );
2372
+ }
2373
+
2060
2374
  assert(ownership.owners?.some(owner => owner.id === vertical.id && owner.path === vertical.path), `${vertical.id} ownership entry is missing`);
2061
2375
  assert(overlay.ports?.[vertical.id], `${vertical.id} development port is missing`);
2062
2376
  assert(overlay.manifests?.[vertical.id]?.includes('/mf-manifest.json'), `${vertical.id} development manifest is missing`);