@bleedingdev/modern-js-create 3.5.0-ultramodern.3 → 3.5.0-ultramodern.30
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 +21 -17
- package/dist/cjs/ultramodern-tooling/commands.cjs +772 -75
- package/dist/cjs/ultramodern-tooling/config.cjs +149 -8
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +25 -0
- package/dist/cjs/ultramodern-workspace/api.cjs +44 -0
- package/dist/cjs/ultramodern-workspace/app-files.cjs +8 -11
- package/dist/cjs/ultramodern-workspace/backend-federation.cjs +282 -0
- package/dist/cjs/ultramodern-workspace/contracts.cjs +24 -8
- package/dist/cjs/ultramodern-workspace/delivery-unit-sync.cjs +157 -0
- package/dist/cjs/ultramodern-workspace/delivery-unit.cjs +88 -0
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +24 -0
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +161 -50
- package/dist/cjs/ultramodern-workspace/package-json.cjs +26 -43
- package/dist/cjs/ultramodern-workspace/pnpm-workspace-policy-plan.cjs +166 -0
- package/dist/cjs/ultramodern-workspace/tooling-command-catalog.cjs +153 -0
- package/dist/cjs/ultramodern-workspace/versions.cjs +8 -3
- package/dist/cjs/ultramodern-workspace/workspace-script-plan.cjs +175 -0
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +104 -63
- package/dist/cjs/ultramodern-workspace/workspace-validation-contract.cjs +97 -0
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +11 -3
- package/dist/cjs/ultramodern-workspace/zerops.cjs +100 -0
- package/dist/esm/ultramodern-tooling/commands.js +775 -78
- package/dist/esm/ultramodern-tooling/config.js +144 -9
- package/dist/esm/ultramodern-workspace/add-vertical.js +26 -1
- package/dist/esm/ultramodern-workspace/api.js +43 -2
- package/dist/esm/ultramodern-workspace/app-files.js +8 -11
- package/dist/esm/ultramodern-workspace/backend-federation.js +217 -0
- package/dist/esm/ultramodern-workspace/contracts.js +24 -8
- package/dist/esm/ultramodern-workspace/delivery-unit-sync.js +108 -0
- package/dist/esm/ultramodern-workspace/delivery-unit.js +31 -0
- package/dist/esm/ultramodern-workspace/descriptors.js +13 -1
- package/dist/esm/ultramodern-workspace/module-federation.js +160 -42
- package/dist/esm/ultramodern-workspace/package-json.js +16 -40
- package/dist/esm/ultramodern-workspace/pnpm-workspace-policy-plan.js +113 -0
- package/dist/esm/ultramodern-workspace/tooling-command-catalog.js +100 -0
- package/dist/esm/ultramodern-workspace/versions.js +6 -4
- package/dist/esm/ultramodern-workspace/workspace-script-plan.js +110 -0
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +88 -65
- package/dist/esm/ultramodern-workspace/workspace-validation-contract.js +59 -0
- package/dist/esm/ultramodern-workspace/write-workspace.js +14 -6
- package/dist/esm/ultramodern-workspace/zerops.js +62 -0
- package/dist/esm-node/ultramodern-tooling/commands.js +775 -78
- package/dist/esm-node/ultramodern-tooling/config.js +144 -9
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +26 -1
- package/dist/esm-node/ultramodern-workspace/api.js +43 -2
- package/dist/esm-node/ultramodern-workspace/app-files.js +8 -11
- package/dist/esm-node/ultramodern-workspace/backend-federation.js +218 -0
- package/dist/esm-node/ultramodern-workspace/contracts.js +24 -8
- package/dist/esm-node/ultramodern-workspace/delivery-unit-sync.js +109 -0
- package/dist/esm-node/ultramodern-workspace/delivery-unit.js +32 -0
- package/dist/esm-node/ultramodern-workspace/descriptors.js +13 -1
- package/dist/esm-node/ultramodern-workspace/module-federation.js +160 -42
- package/dist/esm-node/ultramodern-workspace/package-json.js +16 -40
- package/dist/esm-node/ultramodern-workspace/pnpm-workspace-policy-plan.js +114 -0
- package/dist/esm-node/ultramodern-workspace/tooling-command-catalog.js +101 -0
- package/dist/esm-node/ultramodern-workspace/versions.js +6 -4
- package/dist/esm-node/ultramodern-workspace/workspace-script-plan.js +111 -0
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +88 -65
- package/dist/esm-node/ultramodern-workspace/workspace-validation-contract.js +60 -0
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +14 -6
- package/dist/esm-node/ultramodern-workspace/zerops.js +63 -0
- package/dist/types/ultramodern-tooling/config.d.ts +6 -0
- package/dist/types/ultramodern-workspace/api.d.ts +1 -0
- package/dist/types/ultramodern-workspace/backend-federation.d.ts +11 -0
- package/dist/types/ultramodern-workspace/contracts.d.ts +1 -1
- package/dist/types/ultramodern-workspace/delivery-unit-sync.d.ts +6 -0
- package/dist/types/ultramodern-workspace/delivery-unit.d.ts +27 -0
- package/dist/types/ultramodern-workspace/descriptors.d.ts +4 -0
- package/dist/types/ultramodern-workspace/module-federation.d.ts +4 -9
- package/dist/types/ultramodern-workspace/package-json.d.ts +0 -1
- package/dist/types/ultramodern-workspace/pnpm-workspace-policy-plan.d.ts +39 -0
- package/dist/types/ultramodern-workspace/tooling-command-catalog.d.ts +18 -0
- package/dist/types/ultramodern-workspace/versions.d.ts +5 -3
- package/dist/types/ultramodern-workspace/workspace-script-plan.d.ts +72 -0
- package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +13 -0
- package/dist/types/ultramodern-workspace/workspace-validation-contract.d.ts +42 -0
- package/dist/types/ultramodern-workspace/zerops.d.ts +2 -0
- package/package.json +3 -3
- package/template-workspace/.gitignore.handlebars +4 -0
- package/template-workspace/README.md.handlebars +18 -15
- package/template-workspace/patches/@module-federation__bridge-react@2.6.0.patch +140 -0
- package/template-workspace/patches/@module-federation__modern-js-v3@2.6.0.patch +57 -0
- package/template-workspace/patches/drizzle-orm-ts7-strict-declarations.patch +452 -0
- package/template-workspace/patches/effect-schema-error-type-id.patch +18 -0
- package/template-workspace/pnpm-workspace.yaml.handlebars +8 -0
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +11 -3
- package/templates/workspace-scripts/check-ultramodern-api-boundaries.mts +40 -2
- package/templates/workspace-scripts/generate-node-backend-federation.mjs +270 -0
- package/templates/workspace-scripts/materialize-zerops-runtime.mjs +448 -0
- package/templates/workspace-scripts/proof-cloudflare-version.mjs +237 -9
- package/templates/workspace-scripts/proof-node-backend-federation.mjs +729 -0
- package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +188 -2
- package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +434 -18
|
@@ -8,6 +8,10 @@ const packageScope = '{{packageScope}}';
|
|
|
8
8
|
const expectedNodeVersion = '{{nodeVersion}}';
|
|
9
9
|
const tailwindEnabled = {{tailwindEnabledJson}};
|
|
10
10
|
const fullStackVerticals = {{fullStackVerticalsJson}};
|
|
11
|
+
// Backend-federation and Zerops runtime surfaces only exist when the workspace
|
|
12
|
+
// exposes API-bearing verticals. Shell-only workspaces skip their
|
|
13
|
+
// materialization during migrate, so the contract must not require them.
|
|
14
|
+
const hasBackendSurfaces = fullStackVerticals.length > 0;
|
|
11
15
|
const shellNamespace = {{shellNamespaceJson}};
|
|
12
16
|
const oldRemotePaths = {{oldRemotePathsJson}};
|
|
13
17
|
const expectedBuildScript = {{expectedBuildScriptJson}};
|
|
@@ -53,6 +57,14 @@ const assertExists = relativePath => {
|
|
|
53
57
|
const assertNotExists = relativePath => {
|
|
54
58
|
assert(!fs.existsSync(path.join(root, relativePath)), `Unexpected ${relativePath}`);
|
|
55
59
|
};
|
|
60
|
+
const assertAnyOf = relativePaths => {
|
|
61
|
+
assert(
|
|
62
|
+
relativePaths.some(relativePath =>
|
|
63
|
+
fs.existsSync(path.join(root, relativePath)),
|
|
64
|
+
),
|
|
65
|
+
`Missing one of: ${relativePaths.join(', ')}`,
|
|
66
|
+
);
|
|
67
|
+
};
|
|
56
68
|
const formatJson = value =>
|
|
57
69
|
value === undefined ? 'undefined' : JSON.stringify(value);
|
|
58
70
|
const selfCheckFailure = (contract, message, fixArea) =>
|
|
@@ -136,6 +148,28 @@ const buildMarkerFor = app =>
|
|
|
136
148
|
.update(`${packageScope}:${app.packageSuffix}:${app.id}:0.1.0`)
|
|
137
149
|
.digest('hex')
|
|
138
150
|
.slice(0, 16);
|
|
151
|
+
const deliveryUnitIdentityFixArea =
|
|
152
|
+
'regenerate vertical identity from delivery-unit record; do not hand-edit surface markers';
|
|
153
|
+
const deliveryUnitBlock = record => ({
|
|
154
|
+
schemaVersion: record?.schemaVersion,
|
|
155
|
+
kind: record?.kind,
|
|
156
|
+
unitId: record?.unitId,
|
|
157
|
+
packageName: record?.packageName,
|
|
158
|
+
version: record?.version,
|
|
159
|
+
buildMarker: record?.buildMarker,
|
|
160
|
+
sourceRevision: record?.sourceRevision,
|
|
161
|
+
});
|
|
162
|
+
const assertDeliveryUnitMarkerSpread = (source, exportName, contract) => {
|
|
163
|
+
const spreadPattern = new RegExp(
|
|
164
|
+
`export const ${exportName} = \\{\\s*\\.\\.\\.ultramodernDeliveryUnit,`,
|
|
165
|
+
);
|
|
166
|
+
assertSelfCheck(
|
|
167
|
+
spreadPattern.test(source),
|
|
168
|
+
contract,
|
|
169
|
+
`${exportName} must spread ...ultramodernDeliveryUnit rather than hand-forking per-surface fields`,
|
|
170
|
+
deliveryUnitIdentityFixArea,
|
|
171
|
+
);
|
|
172
|
+
};
|
|
139
173
|
const normalizeCompactApp = rawApp => {
|
|
140
174
|
const id = String(rawApp.id);
|
|
141
175
|
const kind = rawApp.kind === 'vertical' ? 'vertical' : 'shell';
|
|
@@ -486,7 +520,7 @@ const createAppConfigContract = app => ({
|
|
|
486
520
|
'zephyrRspackPlugin',
|
|
487
521
|
],
|
|
488
522
|
dev: {
|
|
489
|
-
assetPrefix: '/',
|
|
523
|
+
assetPrefix: app.kind === 'shell' ? '/' : 'app-public-origin',
|
|
490
524
|
},
|
|
491
525
|
output: {
|
|
492
526
|
assetPrefix: {
|
|
@@ -772,6 +806,107 @@ const expectedManifestUrl = vertical =>
|
|
|
772
806
|
`http://localhost:${vertical.port}/mf-manifest.json`;
|
|
773
807
|
const expectedApiUrl = vertical =>
|
|
774
808
|
`http://localhost:${vertical.port}${vertical.apiPrefix}`;
|
|
809
|
+
const expectedBackendFederationName = vertical => `${vertical.mfName}Backend`;
|
|
810
|
+
const expectedBackendManifestUrl = vertical =>
|
|
811
|
+
`http://localhost:${vertical.port}/backend-mf-manifest.json`;
|
|
812
|
+
const expectedBackendContainerEntry = vertical =>
|
|
813
|
+
`http://localhost:${vertical.port}/backendRemoteEntry.mjs`;
|
|
814
|
+
const expectedBackendManifestEnv = vertical =>
|
|
815
|
+
`VERTICAL_${toEnvSegment(vertical.domain ?? vertical.id)}_BACKEND_MF_MANIFEST`;
|
|
816
|
+
const expectedPublicUrlEnv = vertical =>
|
|
817
|
+
`ULTRAMODERN_PUBLIC_URL_${toEnvSegment(vertical.id)}`;
|
|
818
|
+
const expectedCloudflareWorkerName = vertical =>
|
|
819
|
+
toKebabCase(`${packageScope}-${vertical.packageSuffix ?? vertical.id}`).slice(
|
|
820
|
+
0,
|
|
821
|
+
63,
|
|
822
|
+
);
|
|
823
|
+
const backendFederationSubset = backendFederation => ({
|
|
824
|
+
role: backendFederation?.role,
|
|
825
|
+
name: backendFederation?.name,
|
|
826
|
+
runtimeFramework: backendFederation?.runtimeFramework,
|
|
827
|
+
strictEffectApproach: backendFederation?.strictEffectApproach,
|
|
828
|
+
exposeRuntime: backendFederation?.exposes?.['./effect-api']?.runtime,
|
|
829
|
+
exposeReadiness: backendFederation?.exposes?.['./effect-api']?.readiness,
|
|
830
|
+
versionBoundary: {
|
|
831
|
+
invariant: backendFederation?.versionBoundary?.invariant,
|
|
832
|
+
uiManifestUrl: backendFederation?.versionBoundary?.ui?.manifestUrl,
|
|
833
|
+
apiReadiness: backendFederation?.versionBoundary?.api?.readiness,
|
|
834
|
+
},
|
|
835
|
+
cloudflare: {
|
|
836
|
+
kind: backendFederation?.executionSurfaces?.cloudflare?.kind,
|
|
837
|
+
workerName: backendFederation?.executionSurfaces?.cloudflare?.workerName,
|
|
838
|
+
publicUrlEnv:
|
|
839
|
+
backendFederation?.executionSurfaces?.cloudflare?.publicUrlEnv,
|
|
840
|
+
zephyrRuntime:
|
|
841
|
+
backendFederation?.executionSurfaces?.cloudflare?.zephyr?.runtime,
|
|
842
|
+
},
|
|
843
|
+
node: {
|
|
844
|
+
kind: backendFederation?.executionSurfaces?.node?.kind,
|
|
845
|
+
remoteName: backendFederation?.executionSurfaces?.node?.remoteName,
|
|
846
|
+
manifestEnv: backendFederation?.executionSurfaces?.node?.manifestEnv,
|
|
847
|
+
manifestUrl: backendFederation?.executionSurfaces?.node?.manifestUrl,
|
|
848
|
+
containerEntry:
|
|
849
|
+
backendFederation?.executionSurfaces?.node?.containerEntry,
|
|
850
|
+
remoteType: backendFederation?.executionSurfaces?.node?.remoteType,
|
|
851
|
+
expose: backendFederation?.executionSurfaces?.node?.expose,
|
|
852
|
+
},
|
|
853
|
+
compatibility: {
|
|
854
|
+
contractVersion: backendFederation?.compatibility?.contractVersion,
|
|
855
|
+
},
|
|
856
|
+
topLevelManifestUrl: backendFederation?.manifestUrl,
|
|
857
|
+
topLevelContainerEntry: backendFederation?.containerEntry,
|
|
858
|
+
});
|
|
859
|
+
const expectedBackendFederationSubset = vertical => ({
|
|
860
|
+
role: 'microvertical-server',
|
|
861
|
+
name: expectedBackendFederationName(vertical),
|
|
862
|
+
runtimeFramework: 'effect',
|
|
863
|
+
strictEffectApproach: true,
|
|
864
|
+
exposeRuntime: `${vertical.path}/api/index.ts`,
|
|
865
|
+
exposeReadiness: `${vertical.apiPrefix}/${vertical.stem}/readiness`,
|
|
866
|
+
versionBoundary: {
|
|
867
|
+
invariant: 'web-and-api-same-build',
|
|
868
|
+
uiManifestUrl: expectedManifestUrl(vertical),
|
|
869
|
+
apiReadiness: `${vertical.apiPrefix}/${vertical.stem}/readiness`,
|
|
870
|
+
},
|
|
871
|
+
cloudflare: {
|
|
872
|
+
kind: 'cloudflare-worker-snapshot',
|
|
873
|
+
workerName: expectedCloudflareWorkerName(vertical),
|
|
874
|
+
publicUrlEnv: expectedPublicUrlEnv(vertical),
|
|
875
|
+
zephyrRuntime: 'ssr-worker',
|
|
876
|
+
},
|
|
877
|
+
node: {
|
|
878
|
+
kind: 'node-mf-runtime',
|
|
879
|
+
remoteName: expectedBackendFederationName(vertical),
|
|
880
|
+
manifestEnv: expectedBackendManifestEnv(vertical),
|
|
881
|
+
manifestUrl: expectedBackendManifestUrl(vertical),
|
|
882
|
+
containerEntry: expectedBackendContainerEntry(vertical),
|
|
883
|
+
remoteType: 'module',
|
|
884
|
+
expose: './effect-api',
|
|
885
|
+
},
|
|
886
|
+
compatibility: {
|
|
887
|
+
contractVersion: 'microvertical-server-effect-v1',
|
|
888
|
+
},
|
|
889
|
+
topLevelManifestUrl: undefined,
|
|
890
|
+
topLevelContainerEntry: undefined,
|
|
891
|
+
});
|
|
892
|
+
const serverExecutionSubset = serverExecution => ({
|
|
893
|
+
apiBaseUrl: serverExecution?.apiBaseUrl,
|
|
894
|
+
versionBoundary: serverExecution?.versionBoundary,
|
|
895
|
+
cloudflareKind: serverExecution?.cloudflare?.kind,
|
|
896
|
+
cloudflareWorkerName: serverExecution?.cloudflare?.workerName,
|
|
897
|
+
nodeKind: serverExecution?.node?.kind,
|
|
898
|
+
nodeManifestUrl: serverExecution?.node?.manifestUrl,
|
|
899
|
+
nodeContainerEntry: serverExecution?.node?.containerEntry,
|
|
900
|
+
});
|
|
901
|
+
const expectedServerExecutionSubset = vertical => ({
|
|
902
|
+
apiBaseUrl: expectedApiUrl(vertical),
|
|
903
|
+
versionBoundary: 'web-and-api-same-build',
|
|
904
|
+
cloudflareKind: 'cloudflare-worker-snapshot',
|
|
905
|
+
cloudflareWorkerName: expectedCloudflareWorkerName(vertical),
|
|
906
|
+
nodeKind: 'node-mf-runtime',
|
|
907
|
+
nodeManifestUrl: expectedBackendManifestUrl(vertical),
|
|
908
|
+
nodeContainerEntry: expectedBackendContainerEntry(vertical),
|
|
909
|
+
});
|
|
775
910
|
const remoteContractSubset = remote => ({
|
|
776
911
|
id: remote?.id,
|
|
777
912
|
name: remote?.name,
|
|
@@ -791,9 +926,11 @@ const requiredMicroVerticalPaths = vertical => [
|
|
|
791
926
|
`${vertical.path}/package.json`,
|
|
792
927
|
`${vertical.path}/tsconfig.json`,
|
|
793
928
|
`${vertical.path}/tsconfig.mf-types.json`,
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
929
|
+
`${vertical.path}/modern.config.ts`,
|
|
930
|
+
`${vertical.path}/module-federation.config.ts`,
|
|
931
|
+
`${vertical.path}/backend-federation.config.ts`,
|
|
932
|
+
`${vertical.path}/api/effect-api.ts`,
|
|
933
|
+
`${vertical.path}/api/index.ts`,
|
|
797
934
|
`${vertical.path}/shared/api.ts`,
|
|
798
935
|
`${vertical.path}/src/api/${vertical.stem}-client.ts`,
|
|
799
936
|
`${vertical.path}/src/modern-app-env.d.ts`,
|
|
@@ -988,6 +1125,34 @@ const assertMicroVerticalContractGraph = ({
|
|
|
988
1125
|
`topology/reference-topology.json verticals.${vertical.id}`,
|
|
989
1126
|
'restore generated topology vertical entries',
|
|
990
1127
|
);
|
|
1128
|
+
assertSameJson(
|
|
1129
|
+
backendFederationSubset(topologyEntry.backendFederation),
|
|
1130
|
+
expectedBackendFederationSubset(vertical),
|
|
1131
|
+
`topology/reference-topology.json verticals.${vertical.id}.backendFederation`,
|
|
1132
|
+
'restore generated MicroVertical server execution contract',
|
|
1133
|
+
);
|
|
1134
|
+
|
|
1135
|
+
if (vertical.deliveryUnit) {
|
|
1136
|
+
const expectedDeliveryUnit = deliveryUnitBlock(vertical.deliveryUnit);
|
|
1137
|
+
assertSameJson(
|
|
1138
|
+
deliveryUnitBlock(topologyEntry.deliveryUnit),
|
|
1139
|
+
expectedDeliveryUnit,
|
|
1140
|
+
`topology/reference-topology.json verticals.${vertical.id}.deliveryUnit`,
|
|
1141
|
+
deliveryUnitIdentityFixArea,
|
|
1142
|
+
);
|
|
1143
|
+
assertSameJson(
|
|
1144
|
+
deliveryUnitBlock(topologyEntry.backendFederation?.deliveryUnit),
|
|
1145
|
+
expectedDeliveryUnit,
|
|
1146
|
+
`topology/reference-topology.json verticals.${vertical.id}.backendFederation.deliveryUnit`,
|
|
1147
|
+
deliveryUnitIdentityFixArea,
|
|
1148
|
+
);
|
|
1149
|
+
assertSelfCheck(
|
|
1150
|
+
topologyEntry.backendFederation?.versionBoundary?.identityRoot === 'deliveryUnit',
|
|
1151
|
+
`topology/reference-topology.json verticals.${vertical.id}.backendFederation.versionBoundary.identityRoot`,
|
|
1152
|
+
`Expected "deliveryUnit", found ${formatJson(topologyEntry.backendFederation?.versionBoundary?.identityRoot)}`,
|
|
1153
|
+
deliveryUnitIdentityFixArea,
|
|
1154
|
+
);
|
|
1155
|
+
}
|
|
991
1156
|
|
|
992
1157
|
assertObject(
|
|
993
1158
|
ownershipEntry,
|
|
@@ -1025,6 +1190,13 @@ const assertMicroVerticalContractGraph = ({
|
|
|
1025
1190
|
`topology/local-overlays/development.json apis.${vertical.id}`,
|
|
1026
1191
|
'restore generated local API overlay',
|
|
1027
1192
|
);
|
|
1193
|
+
assertSameJson(
|
|
1194
|
+
serverExecutionSubset(overlay.serverExecution?.[vertical.id]),
|
|
1195
|
+
expectedServerExecutionSubset(vertical),
|
|
1196
|
+
`topology/local-overlays/development.json serverExecution.${vertical.id}`,
|
|
1197
|
+
'restore generated local MicroVertical server execution overlay',
|
|
1198
|
+
);
|
|
1199
|
+
|
|
1028
1200
|
|
|
1029
1201
|
assertSameJson(
|
|
1030
1202
|
shellPackage.dependencies?.[vertical.packageName],
|
|
@@ -1116,6 +1288,7 @@ const assertProjectReferenceEmitConfig = (tsConfig, packagePath) => {
|
|
|
1116
1288
|
);
|
|
1117
1289
|
};
|
|
1118
1290
|
const assertTsConfigReferenceGraph = () => {
|
|
1291
|
+
const baseTsConfig = readJson('tsconfig.base.json');
|
|
1119
1292
|
const rootTsConfig = readJson('tsconfig.json');
|
|
1120
1293
|
const shellTsConfig = readJson('apps/shell-super-app/tsconfig.json');
|
|
1121
1294
|
const shellMfTypesTsConfig = readJson(
|
|
@@ -1151,6 +1324,13 @@ const assertTsConfigReferenceGraph = () => {
|
|
|
1151
1324
|
'apps/shell-super-app/tsconfig.json references',
|
|
1152
1325
|
'restore the generated shell project-reference graph',
|
|
1153
1326
|
);
|
|
1327
|
+
assert(baseTsConfig.compilerOptions?.skipLibCheck !== true, 'tsconfig.base.json must not use skipLibCheck');
|
|
1328
|
+
assertSameJson(
|
|
1329
|
+
shellTsConfig.include ?? [],
|
|
1330
|
+
['src', 'locales/**/*.json', 'package.json', 'shared'],
|
|
1331
|
+
'apps/shell-super-app/tsconfig.json include',
|
|
1332
|
+
'restore the generated shell typecheck boundary',
|
|
1333
|
+
);
|
|
1154
1334
|
assertProjectReferenceEmitConfig(
|
|
1155
1335
|
shellTsConfig,
|
|
1156
1336
|
'apps/shell-super-app',
|
|
@@ -1158,7 +1338,7 @@ const assertTsConfigReferenceGraph = () => {
|
|
|
1158
1338
|
assertSameJson(
|
|
1159
1339
|
shellMfTypesTsConfig,
|
|
1160
1340
|
{
|
|
1161
|
-
extends: '
|
|
1341
|
+
extends: '../../tsconfig.base.json',
|
|
1162
1342
|
include: ['src/modern-app-env.d.ts'],
|
|
1163
1343
|
},
|
|
1164
1344
|
'apps/shell-super-app/tsconfig.mf-types.json',
|
|
@@ -1191,11 +1371,17 @@ const assertTsConfigReferenceGraph = () => {
|
|
|
1191
1371
|
`${vertical.path}/tsconfig.json references`,
|
|
1192
1372
|
'restore the generated MicroVertical project-reference graph',
|
|
1193
1373
|
);
|
|
1374
|
+
assertSameJson(
|
|
1375
|
+
verticalTsConfig.include ?? [],
|
|
1376
|
+
['src', 'locales/**/*.json', 'package.json', 'shared', 'api'],
|
|
1377
|
+
`${vertical.path}/tsconfig.json include`,
|
|
1378
|
+
'restore the generated MicroVertical typecheck boundary',
|
|
1379
|
+
);
|
|
1194
1380
|
assertProjectReferenceEmitConfig(verticalTsConfig, vertical.path);
|
|
1195
1381
|
assertSameJson(
|
|
1196
1382
|
verticalMfTypesTsConfig,
|
|
1197
1383
|
{
|
|
1198
|
-
extends: '
|
|
1384
|
+
extends: '../../tsconfig.base.json',
|
|
1199
1385
|
include: [
|
|
1200
1386
|
'src/federation-entry.tsx',
|
|
1201
1387
|
...vertical.componentPaths.map(componentPath =>
|
|
@@ -1216,7 +1402,7 @@ const packageJsonFiles = startDir => {
|
|
|
1216
1402
|
const current = queue.shift();
|
|
1217
1403
|
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
1218
1404
|
if (
|
|
1219
|
-
|
|
1405
|
+
['.git', '.output', '.zerops', 'dist', 'node_modules', 'repos'].includes(
|
|
1220
1406
|
entry.name,
|
|
1221
1407
|
)
|
|
1222
1408
|
) {
|
|
@@ -1349,6 +1535,33 @@ const extractAssetPrefixExpression = modernConfig => {
|
|
|
1349
1535
|
assert(match?.groups?.expression, 'modern.config.ts must assign assetPrefix');
|
|
1350
1536
|
return match.groups.expression;
|
|
1351
1537
|
};
|
|
1538
|
+
const assertTargetIsolatedBuildArtifacts = (appId, modernConfig) => {
|
|
1539
|
+
assert(
|
|
1540
|
+
modernConfig.includes("const buildTarget = cloudflareDeployEnabled ? 'cloudflare' : 'web';") &&
|
|
1541
|
+
modernConfig.includes("const buildOutputRoot = cloudflareDeployEnabled ? 'dist-cloudflare' : 'dist';") &&
|
|
1542
|
+
modernConfig.includes('const buildTempDirectory = `node_modules/.modern-js-${appId}-${buildTarget}`;') &&
|
|
1543
|
+
modernConfig.includes('const buildCacheDirectory = `node_modules/.cache/rspack-${appId}-${buildTarget}`;') &&
|
|
1544
|
+
modernConfig.includes('root: buildOutputRoot,') &&
|
|
1545
|
+
modernConfig.includes('tempDir: buildTempDirectory,') &&
|
|
1546
|
+
modernConfig.includes('cacheDigest: [appId, buildTarget],') &&
|
|
1547
|
+
modernConfig.includes('cacheDirectory: buildCacheDirectory,'),
|
|
1548
|
+
`${appId} must isolate build output, Modern temp files, and Rspack cache by app and build target`,
|
|
1549
|
+
);
|
|
1550
|
+
};
|
|
1551
|
+
const assertCloudflareBuildSkipsDeployRebuild = (appId, packageJson) => {
|
|
1552
|
+
assert(
|
|
1553
|
+
packageJson.scripts?.['cloudflare:build']?.includes(
|
|
1554
|
+
'ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy --skip-build',
|
|
1555
|
+
),
|
|
1556
|
+
`${appId} cloudflare:build must deploy with --skip-build after the explicit Cloudflare build`,
|
|
1557
|
+
);
|
|
1558
|
+
assert(
|
|
1559
|
+
!packageJson.scripts?.['cloudflare:build']?.includes(
|
|
1560
|
+
'--target dist && ULTRAMODERN_ZEPHYR=false MODERNJS_DEPLOY=cloudflare modern deploy',
|
|
1561
|
+
),
|
|
1562
|
+
`${appId} cloudflare:build must not write public-surface assets into the normal web dist before deploy`,
|
|
1563
|
+
);
|
|
1564
|
+
};
|
|
1352
1565
|
const stripYamlInlineComment = value => {
|
|
1353
1566
|
let quote = undefined;
|
|
1354
1567
|
for (let index = 0; index < value.length; index += 1) {
|
|
@@ -1462,6 +1675,9 @@ const requiredPaths = [
|
|
|
1462
1675
|
'.gitignore',
|
|
1463
1676
|
'package.json',
|
|
1464
1677
|
'pnpm-workspace.yaml',
|
|
1678
|
+
'patches/@module-federation__modern-js-v3@{{moduleFederationVersion}}.patch',
|
|
1679
|
+
'patches/@module-federation__bridge-react@{{moduleFederationVersion}}.patch',
|
|
1680
|
+
'patches/effect-schema-error-type-id.patch',
|
|
1465
1681
|
'tsconfig.json',
|
|
1466
1682
|
'tsconfig.base.json',
|
|
1467
1683
|
'oxlint.config.ts',
|
|
@@ -1469,8 +1685,6 @@ const requiredPaths = [
|
|
|
1469
1685
|
'.github/renovate.json',
|
|
1470
1686
|
'.github/workflows/ultramodern-workspace-gates.yml',
|
|
1471
1687
|
'.agents/agent-reference-repos.json',
|
|
1472
|
-
'.codex/skills-lock.json',
|
|
1473
|
-
'.codex/rstackjs-agent-skills-LICENSE',
|
|
1474
1688
|
'topology/reference-topology.json',
|
|
1475
1689
|
'topology/ownership.json',
|
|
1476
1690
|
'topology/local-overlays/development.json',
|
|
@@ -1478,13 +1692,17 @@ const requiredPaths = [
|
|
|
1478
1692
|
'scripts/bootstrap-agent-skills.mts',
|
|
1479
1693
|
'scripts/check-ultramodern-api-boundaries.mts',
|
|
1480
1694
|
'scripts/check-ultramodern-i18n-boundaries.mts',
|
|
1695
|
+
...(hasBackendSurfaces ? ['scripts/generate-node-backend-federation.mts'] : []),
|
|
1481
1696
|
'scripts/generate-public-surface-assets.mts',
|
|
1697
|
+
'scripts/generate-tanstack-routes.mts',
|
|
1482
1698
|
'scripts/proof-cloudflare-version.mts',
|
|
1699
|
+
...(hasBackendSurfaces ? ['scripts/proof-node-backend-federation.mts'] : []),
|
|
1483
1700
|
'scripts/setup-agent-reference-repos.mts',
|
|
1484
1701
|
'scripts/ultramodern-performance-readiness.config.mjs',
|
|
1485
1702
|
'scripts/ultramodern-performance-readiness.mts',
|
|
1486
1703
|
'scripts/ultramodern-typecheck.mts',
|
|
1487
1704
|
'scripts/validate-ultramodern-workspace.mts',
|
|
1705
|
+
'scripts/verify-cloudflare-output.mts',
|
|
1488
1706
|
'apps/shell-super-app/package.json',
|
|
1489
1707
|
'apps/shell-super-app/tsconfig.json',
|
|
1490
1708
|
'apps/shell-super-app/tsconfig.mf-types.json',
|
|
@@ -1537,6 +1755,40 @@ for (const vertical of fullStackVerticals) {
|
|
|
1537
1755
|
for (const requiredPath of requiredPaths) {
|
|
1538
1756
|
assertExists(requiredPath);
|
|
1539
1757
|
}
|
|
1758
|
+
// Agent skills metadata may live under .agents/ (agents-standard layout) or
|
|
1759
|
+
// .codex/ (legacy scaffold default).
|
|
1760
|
+
assertAnyOf(['.agents/skills-lock.json', '.codex/skills-lock.json']);
|
|
1761
|
+
assertAnyOf([
|
|
1762
|
+
'.agents/rstackjs-agent-skills-LICENSE',
|
|
1763
|
+
'.codex/rstackjs-agent-skills-LICENSE',
|
|
1764
|
+
]);
|
|
1765
|
+
const pnpmWorkspace = readText('pnpm-workspace.yaml');
|
|
1766
|
+
assert(
|
|
1767
|
+
pnpmWorkspace.includes("'@effect/opentelemetry': {{effectVersion}}"),
|
|
1768
|
+
'pnpm-workspace.yaml must override @effect/opentelemetry to the generated Effect cohort',
|
|
1769
|
+
);
|
|
1770
|
+
assert(
|
|
1771
|
+
pnpmWorkspace.includes('effect: {{effectVersion}}'),
|
|
1772
|
+
'pnpm-workspace.yaml must override effect to the generated Effect cohort',
|
|
1773
|
+
);
|
|
1774
|
+
assert(
|
|
1775
|
+
pnpmWorkspace.includes(
|
|
1776
|
+
"'@module-federation/modern-js-v3@{{moduleFederationVersion}}': patches/@module-federation__modern-js-v3@{{moduleFederationVersion}}.patch",
|
|
1777
|
+
),
|
|
1778
|
+
'pnpm-workspace.yaml must patch the generated Module Federation Modern.js integration cohort',
|
|
1779
|
+
);
|
|
1780
|
+
assert(
|
|
1781
|
+
pnpmWorkspace.includes(
|
|
1782
|
+
"'@module-federation/bridge-react@{{moduleFederationVersion}}': patches/@module-federation__bridge-react@{{moduleFederationVersion}}.patch",
|
|
1783
|
+
),
|
|
1784
|
+
'pnpm-workspace.yaml must patch the generated Module Federation React bridge cohort',
|
|
1785
|
+
);
|
|
1786
|
+
assert(
|
|
1787
|
+
pnpmWorkspace.includes(
|
|
1788
|
+
"'effect@{{effectVersion}}': patches/effect-schema-error-type-id.patch",
|
|
1789
|
+
),
|
|
1790
|
+
'pnpm-workspace.yaml must patch the generated Effect declaration cohort',
|
|
1791
|
+
);
|
|
1540
1792
|
for (const oldRemotePath of oldRemotePaths) {
|
|
1541
1793
|
assertNotExists(oldRemotePath);
|
|
1542
1794
|
}
|
|
@@ -1605,6 +1857,11 @@ assert(
|
|
|
1605
1857
|
expectedModernPackageSpecifier('@modern-js/code-tools'),
|
|
1606
1858
|
'Root must depend on @modern-js/code-tools through package source metadata',
|
|
1607
1859
|
);
|
|
1860
|
+
assert(
|
|
1861
|
+
rootPackage.devDependencies?.['@modern-js/plugin-bff'] ===
|
|
1862
|
+
expectedModernPackageSpecifier('@modern-js/plugin-bff'),
|
|
1863
|
+
'Root must depend on @modern-js/plugin-bff for Node backend federation proof',
|
|
1864
|
+
);
|
|
1608
1865
|
if (packageSource.strategy === 'install') {
|
|
1609
1866
|
const installSpecifier = packageSource.modernPackages?.specifier;
|
|
1610
1867
|
assert(
|
|
@@ -1664,20 +1921,75 @@ if (bridgeConfig) {
|
|
|
1664
1921
|
}
|
|
1665
1922
|
assert(rootPackage.scripts?.['bridge:check'], 'Bridge workspaces must expose bridge:check');
|
|
1666
1923
|
} else {
|
|
1667
|
-
assert(rootPackage.scripts?.typecheck === 'node ./scripts/ultramodern-typecheck.mts --
|
|
1924
|
+
assert(rootPackage.scripts?.typecheck === 'node ./scripts/ultramodern-typecheck.mts --project tsconfig.json', 'Root typecheck must run TS-Go across the root project reference graph');
|
|
1668
1925
|
}
|
|
1669
1926
|
assert(rootPackage.scripts?.['contract:check'] === 'node ./scripts/validate-ultramodern-workspace.mts', 'Root must expose contract:check');
|
|
1670
1927
|
assert(rootPackage.scripts?.['api:check'] === 'node ./scripts/check-ultramodern-api-boundaries.mts', 'Root must expose api:check');
|
|
1671
1928
|
assert(rootPackage.scripts?.['i18n:boundaries'] === 'node ./scripts/check-ultramodern-i18n-boundaries.mts', 'Root must expose i18n:boundaries');
|
|
1672
1929
|
assert(rootPackage.scripts?.['performance:readiness'] === 'node ./scripts/ultramodern-performance-readiness.mts', 'Root must expose default-on performance readiness diagnostics');
|
|
1930
|
+
if (hasBackendSurfaces) {
|
|
1931
|
+
assert(rootPackage.scripts?.['node:backend-federation:generate'] === 'node ./scripts/generate-node-backend-federation.mts', 'Root must expose local Node backend federation artifact generation');
|
|
1932
|
+
assert(rootPackage.scripts?.['zerops:materialize'] === 'node ./scripts/materialize-zerops-runtime.mjs', 'Root must expose Zerops runtime materialization script');
|
|
1933
|
+
assert(
|
|
1934
|
+
rootPackage.scripts?.['node:proof'] ===
|
|
1935
|
+
'node ./scripts/proof-node-backend-federation.mts',
|
|
1936
|
+
'Root must expose node:proof for backend federation Effect modules',
|
|
1937
|
+
);
|
|
1938
|
+
}
|
|
1939
|
+
assertNotExists('scripts/generate-node-backend-federation.mjs');
|
|
1940
|
+
assertNotExists('scripts/proof-node-backend-federation.mjs');
|
|
1941
|
+
assertNotExists('scripts/verify-cloudflare-output.mjs');
|
|
1942
|
+
assertNotExists('scripts/generate-tanstack-routes.mjs');
|
|
1673
1943
|
assert(
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1944
|
+
rootPackage.scripts?.check?.includes('pnpm api:check') &&
|
|
1945
|
+
(hasBackendSurfaces
|
|
1946
|
+
? rootPackage.scripts.check.includes('pnpm node:proof')
|
|
1947
|
+
: !rootPackage.scripts.check.includes('pnpm node:proof')) &&
|
|
1948
|
+
rootPackage.scripts.check.endsWith(
|
|
1949
|
+
bridgeConfig
|
|
1950
|
+
? '&& pnpm performance:readiness && pnpm bridge:check'
|
|
1951
|
+
: '&& pnpm performance:readiness',
|
|
1952
|
+
),
|
|
1953
|
+
'Root check must run Node backend federation proof when backend surfaces exist, default-on performance readiness diagnostics, and bridge gates when configured',
|
|
1680
1954
|
);
|
|
1955
|
+
if (hasBackendSurfaces) {
|
|
1956
|
+
const zeropsYaml = readText('zerops.yaml');
|
|
1957
|
+
assert(zeropsYaml.includes('zerops:'), 'Zerops manifest must include zerops services');
|
|
1958
|
+
assert(zeropsYaml.includes('setup: shell-super-app'), 'Zerops manifest must include shell service');
|
|
1959
|
+
assert(zeropsYaml.includes('base: nodejs@26'), 'Zerops manifest must use generated Node runtime major');
|
|
1960
|
+
assert(zeropsYaml.includes('deployFiles:'), 'Zerops manifest must deploy package-pruned runtime directories');
|
|
1961
|
+
assert(
|
|
1962
|
+
zeropsYaml.includes('start: cd .zerops/runtime/shell-super-app && npm run serve'),
|
|
1963
|
+
'Zerops shell service must start from materialized runtime package',
|
|
1964
|
+
);
|
|
1965
|
+
assert(
|
|
1966
|
+
zeropsYaml.includes('SHELL_SUPER_APP_PORT:') &&
|
|
1967
|
+
zeropsYaml.includes('ULTRAMODERN_ZEROPS_SERVICE:'),
|
|
1968
|
+
'Zerops manifest must expose service identity and ports',
|
|
1969
|
+
);
|
|
1970
|
+
for (const vertical of fullStackVerticals) {
|
|
1971
|
+
assert(zeropsYaml.includes(`setup: ${vertical.id}`), `${vertical.id} must have a Zerops service`);
|
|
1972
|
+
assert(
|
|
1973
|
+
zeropsYaml.includes(`~/.local/bin/mise exec -- pnpm run zerops:materialize -- --app ${vertical.id} --package ${vertical.packageName} --package-dir ${vertical.path}`),
|
|
1974
|
+
`${vertical.id} Zerops service must materialize its runtime package`,
|
|
1975
|
+
);
|
|
1976
|
+
assert(
|
|
1977
|
+
zeropsYaml.includes(`path: ${vertical.apiPrefix}/${vertical.stem}/readiness`),
|
|
1978
|
+
`${vertical.id} BFF readiness path must use generated API prefix`,
|
|
1979
|
+
);
|
|
1980
|
+
}
|
|
1981
|
+
const zeropsMaterializer = readText('scripts/materialize-zerops-runtime.mjs');
|
|
1982
|
+
assert(zeropsMaterializer.includes("MODERNJS_DEPLOY: 'node'"), 'Zerops materializer must use Modern.js Node deploy target');
|
|
1983
|
+
assert(/'deploy',\s*'--skip-build'/.test(zeropsMaterializer), 'Zerops materializer must pass --skip-build directly to Modern deploy');
|
|
1984
|
+
assert(zeropsMaterializer.includes('normalizeRuntimePackageDependencies'), 'Zerops materializer must normalize generated Modern package aliases before installing dependencies');
|
|
1985
|
+
assert(zeropsMaterializer.includes('officialPackageName'), 'Zerops materializer must add official Modern.js npm aliases for generated runtime imports');
|
|
1986
|
+
assert(zeropsMaterializer.includes('installRuntimeDependencies'), 'Zerops materializer must install dependencies outside the workspace copy runtime node_modules');
|
|
1987
|
+
assert(zeropsMaterializer.includes('copyWorkspacePackage'), 'Zerops materializer must preserve generated local workspace package dependencies');
|
|
1988
|
+
assert(zeropsMaterializer.includes('makeWorkspacePackageRuntimeSafe'), 'Zerops materializer must rewrite copied workspace packages to runtime-safe JavaScript exports');
|
|
1989
|
+
assert(zeropsMaterializer.includes("serve: runtimePackage.scripts?.serve ?? 'node index.js'"), 'Zerops materializer must preserve runtime serve script fallback');
|
|
1990
|
+
assert(zeropsMaterializer.includes("'install', '--omit=dev'"), 'Zerops materializer must omit dev dependencies from runtime installs');
|
|
1991
|
+
assert(zeropsMaterializer.includes("'--legacy-peer-deps'"), 'Zerops materializer must tolerate generated-workspace peer dependency ranges in npm runtime install');
|
|
1992
|
+
}
|
|
1681
1993
|
const performanceReadinessConfig = readText('scripts/ultramodern-performance-readiness.config.mjs');
|
|
1682
1994
|
const assertToolWrapper = (scriptPath, command) => {
|
|
1683
1995
|
const source = readText(scriptPath);
|
|
@@ -1708,8 +2020,14 @@ assert(rootPackage.scripts?.postinstall === "oxfmt . '!repos/**' && node ./scrip
|
|
|
1708
2020
|
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');
|
|
1709
2021
|
const agentSkillsBootstrap = readText('scripts/bootstrap-agent-skills.mts');
|
|
1710
2022
|
assertToolWrapper('scripts/assert-mf-types.mts', 'mf-types');
|
|
2023
|
+
if (hasBackendSurfaces) {
|
|
2024
|
+
assertToolWrapper('scripts/generate-node-backend-federation.mts', 'backend-federation-generate');
|
|
2025
|
+
assertToolWrapper('scripts/proof-node-backend-federation.mts', 'backend-federation-proof');
|
|
2026
|
+
}
|
|
1711
2027
|
assertToolWrapper('scripts/generate-public-surface-assets.mts', 'public-surface');
|
|
2028
|
+
assertToolWrapper('scripts/generate-tanstack-routes.mts', 'routes-generate');
|
|
1712
2029
|
assertToolWrapper('scripts/proof-cloudflare-version.mts', 'cloudflare-proof');
|
|
2030
|
+
assertToolWrapper('scripts/verify-cloudflare-output.mts', 'cloudflare-output-verify');
|
|
1713
2031
|
assertToolWrapper('scripts/migrate-strict-effect.mts', 'migrate-strict-effect');
|
|
1714
2032
|
assertToolWrapper('scripts/bootstrap-agent-skills.mts', 'skills');
|
|
1715
2033
|
assert(!agentSkillsBootstrap.includes("run('brew'") && !agentSkillsBootstrap.includes('runShell('), 'Agent skills bootstrap must never invoke system package managers');
|
|
@@ -1751,8 +2069,17 @@ assert(JSON.stringify(generatedContract.performanceReadiness?.signals?.map(signa
|
|
|
1751
2069
|
|
|
1752
2070
|
const shellModernConfig = readText('apps/shell-super-app/modern.config.ts');
|
|
1753
2071
|
const shellModuleFederationConfig = readText('apps/shell-super-app/module-federation.config.ts');
|
|
2072
|
+
const shellModernAppEnv = readText('apps/shell-super-app/src/modern-app-env.d.ts');
|
|
2073
|
+
const gitignore = readText('.gitignore');
|
|
1754
2074
|
const shellRouteHead = readText('apps/shell-super-app/src/routes/ultramodern-route-head.tsx');
|
|
1755
2075
|
const shellRouteMetadata = readText('apps/shell-super-app/src/routes/ultramodern-route-metadata.ts');
|
|
2076
|
+
assert(/^\.mf\/$/mu.test(gitignore), 'Generated .gitignore must ignore root Module Federation diagnostics');
|
|
2077
|
+
assert(/^\*\*\/\.mf\/$/mu.test(gitignore), 'Generated .gitignore must ignore per-app Module Federation diagnostics');
|
|
2078
|
+
assert(/^dist-cloudflare\/$/mu.test(gitignore), 'Generated .gitignore must ignore Cloudflare build output');
|
|
2079
|
+
assert(shellModernAppEnv.includes("import '@modern-js/app-tools/types';"), 'Shell app env must import the framework-owned app ambient type bundle through module resolution');
|
|
2080
|
+
assert(/declare global \{\s*const ULTRAMODERN_SITE_URL: string;\s*\}/u.test(shellModernAppEnv), 'Shell app env must keep generated globals explicit after importing app ambient types');
|
|
2081
|
+
assert(!shellModernAppEnv.includes("declare module '*.svg'"), 'Shell app env must not redeclare framework-owned svg asset modules');
|
|
2082
|
+
assert(!shellModernAppEnv.includes("declare module '*.css'"), 'Shell app env must not redeclare framework-owned css asset modules');
|
|
1756
2083
|
assert(shellRouteMetadata.includes('@generated by @modern-js/create'), 'Shell route metadata compatibility manifest must be marked generated');
|
|
1757
2084
|
assert(shellRouteMetadata.includes("authoring: 'colocated-route-meta'"), 'Shell route metadata manifest must advertise colocated authoring');
|
|
1758
2085
|
const expectedZephyrDependencies = Object.fromEntries(
|
|
@@ -1771,6 +2098,9 @@ assert(shellPackage.dependencies?.['@modern-js/plugin-bff'] === expectedModernPa
|
|
|
1771
2098
|
assert(shellPackage.dependencies?.['@modern-js/plugin-i18n'] === expectedModernPackageSpecifier('@modern-js/plugin-i18n'), 'Shell plugin-i18n dependency must match package source metadata');
|
|
1772
2099
|
assert(shellPackage.dependencies?.['@modern-js/plugin-tanstack'] === expectedModernPackageSpecifier('@modern-js/plugin-tanstack'), 'Shell plugin-tanstack dependency must match package source metadata');
|
|
1773
2100
|
assert(shellPackage.dependencies?.['@modern-js/runtime'] === expectedModernPackageSpecifier('@modern-js/runtime'), 'Shell runtime dependency must match package source metadata');
|
|
2101
|
+
assert(shellPackage.scripts?.['cloudflare:deploy'] === 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json', 'Shell must expose cloudflare:deploy');
|
|
2102
|
+
assertTargetIsolatedBuildArtifacts('shell-super-app', shellModernConfig);
|
|
2103
|
+
assertCloudflareBuildSkipsDeployRebuild('shell-super-app', shellPackage);
|
|
1774
2104
|
const shellContract = generatedContract.apps?.find(app => app.id === 'shell-super-app');
|
|
1775
2105
|
assert(shellContract?.deploy?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell Cloudflare workerName is incorrect');
|
|
1776
2106
|
assert(shellContract?.deploy?.cloudflare?.publicUrlEnv === 'ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP', 'Shell Cloudflare public URL env is incorrect');
|
|
@@ -1841,12 +2171,67 @@ for (const vertical of fullStackVerticals) {
|
|
|
1841
2171
|
const packageJson = readJson(`${vertical.path}/package.json`);
|
|
1842
2172
|
const modernConfig = readText(`${vertical.path}/modern.config.ts`);
|
|
1843
2173
|
const moduleFederationConfig = readText(`${vertical.path}/module-federation.config.ts`);
|
|
2174
|
+
const modernAppEnv = readText(`${vertical.path}/src/modern-app-env.d.ts`);
|
|
1844
2175
|
const routeHead = readText(`${vertical.path}/src/routes/ultramodern-route-head.tsx`);
|
|
1845
2176
|
const routeMetadata = readText(`${vertical.path}/src/routes/ultramodern-route-metadata.ts`);
|
|
2177
|
+
const ultramodernBuildSource = readText(`${vertical.path}/shared/ultramodern-build.ts`);
|
|
2178
|
+
if (vertical.deliveryUnit) {
|
|
2179
|
+
const buildLabel = `${vertical.path}/shared/ultramodern-build.ts ultramodernDeliveryUnit`;
|
|
2180
|
+
assertSelfCheck(
|
|
2181
|
+
ultramodernBuildSource.includes('export const ultramodernDeliveryUnit'),
|
|
2182
|
+
buildLabel,
|
|
2183
|
+
'Missing ultramodernDeliveryUnit export',
|
|
2184
|
+
deliveryUnitIdentityFixArea,
|
|
2185
|
+
);
|
|
2186
|
+
const buildMatch = /\bbuild:\s*'([^']+)'/u.exec(ultramodernBuildSource);
|
|
2187
|
+
const unitIdMatch = /\bunitId:\s*'([^']+)'/u.exec(ultramodernBuildSource);
|
|
2188
|
+
const packageNameMatch = /\bpackageName:\s*'([^']+)'/u.exec(ultramodernBuildSource);
|
|
2189
|
+
const versionMatch = /\bversion:\s*'([^']+)'/u.exec(ultramodernBuildSource);
|
|
2190
|
+
assertSelfCheck(
|
|
2191
|
+
buildMatch?.[1] === vertical.deliveryUnit.buildMarker,
|
|
2192
|
+
buildLabel,
|
|
2193
|
+
`Expected build "${vertical.deliveryUnit.buildMarker}", found ${formatJson(buildMatch?.[1])}`,
|
|
2194
|
+
deliveryUnitIdentityFixArea,
|
|
2195
|
+
);
|
|
2196
|
+
assertSelfCheck(
|
|
2197
|
+
unitIdMatch?.[1] === vertical.deliveryUnit.unitId,
|
|
2198
|
+
buildLabel,
|
|
2199
|
+
`Expected unitId "${vertical.deliveryUnit.unitId}", found ${formatJson(unitIdMatch?.[1])}`,
|
|
2200
|
+
deliveryUnitIdentityFixArea,
|
|
2201
|
+
);
|
|
2202
|
+
assertSelfCheck(
|
|
2203
|
+
packageNameMatch?.[1] === vertical.deliveryUnit.packageName,
|
|
2204
|
+
buildLabel,
|
|
2205
|
+
`Expected packageName "${vertical.deliveryUnit.packageName}", found ${formatJson(packageNameMatch?.[1])}`,
|
|
2206
|
+
deliveryUnitIdentityFixArea,
|
|
2207
|
+
);
|
|
2208
|
+
assertSelfCheck(
|
|
2209
|
+
versionMatch?.[1] === vertical.deliveryUnit.version,
|
|
2210
|
+
buildLabel,
|
|
2211
|
+
`Expected version "${vertical.deliveryUnit.version}", found ${formatJson(versionMatch?.[1])}`,
|
|
2212
|
+
deliveryUnitIdentityFixArea,
|
|
2213
|
+
);
|
|
2214
|
+
assertDeliveryUnitMarkerSpread(
|
|
2215
|
+
ultramodernBuildSource,
|
|
2216
|
+
'ultramodernUiMarker',
|
|
2217
|
+
`${vertical.path}/shared/ultramodern-build.ts ultramodernUiMarker`,
|
|
2218
|
+
);
|
|
2219
|
+
assertDeliveryUnitMarkerSpread(
|
|
2220
|
+
ultramodernBuildSource,
|
|
2221
|
+
'ultramodernApiMarker',
|
|
2222
|
+
`${vertical.path}/shared/ultramodern-build.ts ultramodernApiMarker`,
|
|
2223
|
+
);
|
|
2224
|
+
}
|
|
2225
|
+
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`);
|
|
2226
|
+
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`);
|
|
2227
|
+
assert(!modernAppEnv.includes("declare module '*.svg'"), `${vertical.id} app env must not redeclare framework-owned svg asset modules`);
|
|
2228
|
+
assert(!modernAppEnv.includes("declare module '*.css'"), `${vertical.id} app env must not redeclare framework-owned css asset modules`);
|
|
1846
2229
|
assert(routeMetadata.includes('@generated by @modern-js/create'), `${vertical.id} route metadata compatibility manifest must be marked generated`);
|
|
1847
2230
|
assert(routeMetadata.includes("authoring: 'colocated-route-meta'"), `${vertical.id} route metadata manifest must advertise colocated authoring`);
|
|
1848
2231
|
assert(packageJson.name === vertical.packageName, `${vertical.id} package name is incorrect`);
|
|
1849
2232
|
assert(packageJson.scripts?.['cloudflare:deploy'] === 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json', `${vertical.id} must expose cloudflare:deploy`);
|
|
2233
|
+
assertTargetIsolatedBuildArtifacts(vertical.id, modernConfig);
|
|
2234
|
+
assertCloudflareBuildSkipsDeployRebuild(vertical.id, packageJson);
|
|
1850
2235
|
assert(packageJson.scripts?.['cloudflare:proof']?.includes(`--app ${vertical.id}`), `${vertical.id} must expose cloudflare:proof`);
|
|
1851
2236
|
assert(packageJson.devDependencies?.['@modern-js/app-tools'] === expectedModernPackageSpecifier('@modern-js/app-tools'), `${vertical.id} app-tools dependency must match package source metadata`);
|
|
1852
2237
|
assert(packageJson.dependencies?.['@modern-js/plugin-bff'] === expectedModernPackageSpecifier('@modern-js/plugin-bff'), `${vertical.id} plugin-bff dependency must match package source metadata`);
|
|
@@ -1892,9 +2277,9 @@ for (const vertical of fullStackVerticals) {
|
|
|
1892
2277
|
assert(!verticalAssetPrefixExpression.includes('configuredSiteUrl') && !verticalAssetPrefixExpression.includes('MODERN_PUBLIC_SITE_URL'), `${vertical.id} asset prefix must not fall back to MODERN_PUBLIC_SITE_URL`);
|
|
1893
2278
|
assert(modernConfig.includes(`envValue('ULTRAMODERN_PUBLIC_URL_${vertical.id.replace(/-/g, '_').toUpperCase()}')`), `${vertical.id} asset prefix must read its per-app public URL`);
|
|
1894
2279
|
assert(modernConfig.includes('inferredCloudflareUrl'), `${vertical.id} asset prefix must support workers.dev origin inference`);
|
|
1895
|
-
assert(
|
|
2280
|
+
assert(/dev:\s*\{\s*\/\/ Remote dev manifests must publish an absolute publicPath[\s\S]*?assetPrefix,\s*\}/u.test(modernConfig), `${vertical.id} modern.config.ts must publish dev assets from its own remote origin`);
|
|
1896
2281
|
assert(modernConfig.includes('assetPrefix,'), `${vertical.id} modern.config.ts must wire output.assetPrefix to the derived asset prefix`);
|
|
1897
|
-
assert(contractEntry?.config?.dev?.assetPrefix === '
|
|
2282
|
+
assert(contractEntry?.config?.dev?.assetPrefix === 'app-public-origin', `${vertical.id} dev asset prefix must default to its app public origin`);
|
|
1898
2283
|
assert(contractEntry?.config?.output?.assetPrefix?.default === 'app-public-origin', `${vertical.id} asset prefix must default to its app public origin`);
|
|
1899
2284
|
assert(JSON.stringify(contractEntry?.config?.output?.assetPrefix?.envFallbackOrder) === JSON.stringify(['MODERN_ASSET_PREFIX', 'ULTRAMODERN_ASSET_PREFIX']), `${vertical.id} asset prefix env fallback order is incorrect`);
|
|
1900
2285
|
assert(contractEntry?.config?.output?.disableTsChecker === false, `${vertical.id} must keep the framework TypeScript checker enabled`);
|
|
@@ -1967,6 +2352,37 @@ for (const vertical of fullStackVerticals) {
|
|
|
1967
2352
|
assert(topologyEntry?.api?.readiness?.endpoint === `/${vertical.stem}/readiness`, `${vertical.id} topology readiness endpoint is incorrect`);
|
|
1968
2353
|
assert(Object.keys(topologyEntry?.api?.domainOperations ?? {}).length >= 3, `${vertical.id} topology domain operations are missing`);
|
|
1969
2354
|
|
|
2355
|
+
if (vertical.deliveryUnit) {
|
|
2356
|
+
const expectedDeliveryUnit = deliveryUnitBlock(vertical.deliveryUnit);
|
|
2357
|
+
const compactAppEntry = ultramodernConfig.topology?.apps?.find(
|
|
2358
|
+
entry => entry?.id === vertical.id,
|
|
2359
|
+
);
|
|
2360
|
+
assertSameJson(
|
|
2361
|
+
deliveryUnitBlock(compactAppEntry?.deliveryUnit),
|
|
2362
|
+
expectedDeliveryUnit,
|
|
2363
|
+
`${compactConfigPath} topology.apps.${vertical.id}.deliveryUnit`,
|
|
2364
|
+
deliveryUnitIdentityFixArea,
|
|
2365
|
+
);
|
|
2366
|
+
assertSameJson(
|
|
2367
|
+
deliveryUnitBlock(compactAppEntry?.backendFederation?.deliveryUnit),
|
|
2368
|
+
expectedDeliveryUnit,
|
|
2369
|
+
`${compactConfigPath} topology.apps.${vertical.id}.backendFederation.deliveryUnit`,
|
|
2370
|
+
deliveryUnitIdentityFixArea,
|
|
2371
|
+
);
|
|
2372
|
+
assertSelfCheck(
|
|
2373
|
+
compactAppEntry?.backendFederation?.versionBoundary?.identityRoot === 'deliveryUnit',
|
|
2374
|
+
`${compactConfigPath} topology.apps.${vertical.id}.backendFederation.versionBoundary.identityRoot`,
|
|
2375
|
+
`Expected "deliveryUnit", found ${formatJson(compactAppEntry?.backendFederation?.versionBoundary?.identityRoot)}`,
|
|
2376
|
+
deliveryUnitIdentityFixArea,
|
|
2377
|
+
);
|
|
2378
|
+
assertSameJson(
|
|
2379
|
+
deliveryUnitBlock(compactAppEntry?.deliveryUnit),
|
|
2380
|
+
deliveryUnitBlock(topologyEntry?.deliveryUnit),
|
|
2381
|
+
`${compactConfigPath} vs topology/reference-topology.json verticals.${vertical.id}.deliveryUnit`,
|
|
2382
|
+
deliveryUnitIdentityFixArea,
|
|
2383
|
+
);
|
|
2384
|
+
}
|
|
2385
|
+
|
|
1970
2386
|
assert(ownership.owners?.some(owner => owner.id === vertical.id && owner.path === vertical.path), `${vertical.id} ownership entry is missing`);
|
|
1971
2387
|
assert(overlay.ports?.[vertical.id], `${vertical.id} development port is missing`);
|
|
1972
2388
|
assert(overlay.manifests?.[vertical.id]?.includes('/mf-manifest.json'), `${vertical.id} development manifest is missing`);
|