@bleedingdev/modern-js-create 3.2.0-ultramodern.31 → 3.2.0-ultramodern.32
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 +98 -1
- package/dist/index.js +1832 -181
- package/package.json +1 -1
- package/template/README.md +95 -0
package/dist/index.js
CHANGED
|
@@ -607,6 +607,9 @@ const modernPackageNames = [
|
|
|
607
607
|
'@modern-js/runtime'
|
|
608
608
|
];
|
|
609
609
|
const ULTRAMODERN_WORKSPACE_FLAG = '--ultramodern-workspace';
|
|
610
|
+
function isRecord(value) {
|
|
611
|
+
return null !== value && 'object' == typeof value && !Array.isArray(value);
|
|
612
|
+
}
|
|
610
613
|
const shellApp = {
|
|
611
614
|
id: 'shell-super-app',
|
|
612
615
|
directory: 'apps/shell-super-app',
|
|
@@ -617,9 +620,9 @@ const shellApp = {
|
|
|
617
620
|
port: 3020,
|
|
618
621
|
mfName: 'shellSuperApp',
|
|
619
622
|
remoteRefs: [
|
|
620
|
-
'remote-
|
|
621
|
-
'remote-
|
|
622
|
-
'remote-
|
|
623
|
+
'remote-explore',
|
|
624
|
+
'remote-decide',
|
|
625
|
+
'remote-checkout'
|
|
623
626
|
],
|
|
624
627
|
ownership: {
|
|
625
628
|
team: 'super-app-platform',
|
|
@@ -638,104 +641,123 @@ const shellApp = {
|
|
|
638
641
|
};
|
|
639
642
|
const remoteApps = [
|
|
640
643
|
{
|
|
641
|
-
id: 'remote-
|
|
642
|
-
directory: 'apps/remotes/remote-
|
|
643
|
-
packageSuffix: 'remote-
|
|
644
|
-
displayName: '
|
|
644
|
+
id: 'remote-explore',
|
|
645
|
+
directory: 'apps/remotes/remote-explore',
|
|
646
|
+
packageSuffix: 'remote-explore',
|
|
647
|
+
displayName: 'Explore Remote',
|
|
645
648
|
kind: 'vertical',
|
|
646
|
-
domain: '
|
|
647
|
-
portEnv: '
|
|
649
|
+
domain: 'explore',
|
|
650
|
+
portEnv: 'REMOTE_EXPLORE_PORT',
|
|
648
651
|
port: 3021,
|
|
649
|
-
mfName: '
|
|
652
|
+
mfName: 'remoteExplore',
|
|
650
653
|
exposes: {
|
|
654
|
+
'./Footer': './src/components/footer.tsx',
|
|
655
|
+
'./Header': './src/components/header.tsx',
|
|
656
|
+
'./Recommendations': './src/components/recommendations.tsx',
|
|
651
657
|
'./Route': './src/remote-entry.tsx',
|
|
652
|
-
'./
|
|
658
|
+
'./StorePicker': './src/components/store-picker.tsx'
|
|
653
659
|
},
|
|
654
660
|
effectApi: {
|
|
655
|
-
stem: '
|
|
656
|
-
prefix: '/
|
|
661
|
+
stem: 'explore',
|
|
662
|
+
prefix: '/explore-api',
|
|
657
663
|
consumedBy: [
|
|
658
664
|
shellApp.id,
|
|
659
|
-
'remote-
|
|
665
|
+
'remote-explore'
|
|
660
666
|
]
|
|
661
667
|
},
|
|
662
668
|
ownership: {
|
|
663
|
-
team: '
|
|
664
|
-
slack: '#
|
|
665
|
-
pagerDuty: 'pd-
|
|
666
|
-
runbookRef: 'runbooks/wave2/remote-
|
|
667
|
-
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#remote-
|
|
669
|
+
team: 'tractor-explore',
|
|
670
|
+
slack: '#tractor-explore',
|
|
671
|
+
pagerDuty: 'pd-tractor-explore',
|
|
672
|
+
runbookRef: 'runbooks/wave2/remote-explore.md',
|
|
673
|
+
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#remote-explore',
|
|
668
674
|
blastRadius: {
|
|
669
|
-
tier: 'tier-1-
|
|
675
|
+
tier: 'tier-1-tractor-discovery',
|
|
670
676
|
references: [
|
|
671
|
-
'docs/super-app-rfc-adr/wave2/blast-radius.md#
|
|
672
|
-
'docs/super-app-rfc-adr/wave2/rollback.md#
|
|
677
|
+
'docs/super-app-rfc-adr/wave2/blast-radius.md#explore',
|
|
678
|
+
'docs/super-app-rfc-adr/wave2/rollback.md#explore-lkg'
|
|
673
679
|
]
|
|
674
680
|
}
|
|
675
681
|
}
|
|
676
682
|
},
|
|
677
683
|
{
|
|
678
|
-
id: 'remote-
|
|
679
|
-
directory: 'apps/remotes/remote-
|
|
680
|
-
packageSuffix: 'remote-
|
|
681
|
-
displayName: '
|
|
684
|
+
id: 'remote-decide',
|
|
685
|
+
directory: 'apps/remotes/remote-decide',
|
|
686
|
+
packageSuffix: 'remote-decide',
|
|
687
|
+
displayName: 'Decide Remote',
|
|
682
688
|
kind: 'vertical',
|
|
683
|
-
domain: '
|
|
684
|
-
portEnv: '
|
|
689
|
+
domain: 'decide',
|
|
690
|
+
portEnv: 'REMOTE_DECIDE_PORT',
|
|
685
691
|
port: 3022,
|
|
686
|
-
mfName: '
|
|
692
|
+
mfName: 'remoteDecide',
|
|
693
|
+
remoteRefs: [
|
|
694
|
+
'remote-explore',
|
|
695
|
+
'remote-checkout'
|
|
696
|
+
],
|
|
687
697
|
exposes: {
|
|
688
|
-
'./
|
|
689
|
-
'./
|
|
698
|
+
'./ProductPage': './src/components/product-page.tsx',
|
|
699
|
+
'./Route': './src/remote-entry.tsx'
|
|
690
700
|
},
|
|
691
701
|
effectApi: {
|
|
692
|
-
stem: '
|
|
693
|
-
prefix: '/
|
|
702
|
+
stem: 'decide',
|
|
703
|
+
prefix: '/decide-api',
|
|
694
704
|
consumedBy: [
|
|
695
705
|
shellApp.id,
|
|
696
|
-
'remote-
|
|
706
|
+
'remote-decide'
|
|
697
707
|
]
|
|
698
708
|
},
|
|
699
709
|
ownership: {
|
|
700
|
-
team: '
|
|
701
|
-
slack: '#
|
|
702
|
-
pagerDuty: 'pd-
|
|
703
|
-
runbookRef: 'runbooks/wave2/remote-
|
|
704
|
-
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#remote-
|
|
710
|
+
team: 'tractor-decide',
|
|
711
|
+
slack: '#tractor-decide',
|
|
712
|
+
pagerDuty: 'pd-tractor-decide',
|
|
713
|
+
runbookRef: 'runbooks/wave2/remote-decide.md',
|
|
714
|
+
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#remote-decide',
|
|
705
715
|
blastRadius: {
|
|
706
|
-
tier: 'tier-
|
|
716
|
+
tier: 'tier-1-tractor-configuration',
|
|
707
717
|
references: [
|
|
708
|
-
'docs/super-app-rfc-adr/wave2/blast-radius.md#
|
|
709
|
-
'docs/super-app-rfc-adr/wave2/rollback.md#
|
|
718
|
+
'docs/super-app-rfc-adr/wave2/blast-radius.md#decide',
|
|
719
|
+
'docs/super-app-rfc-adr/wave2/rollback.md#decide-lkg'
|
|
710
720
|
]
|
|
711
721
|
}
|
|
712
722
|
}
|
|
713
723
|
},
|
|
714
724
|
{
|
|
715
|
-
id: 'remote-
|
|
716
|
-
directory: 'apps/remotes/remote-
|
|
717
|
-
packageSuffix: 'remote-
|
|
718
|
-
displayName: '
|
|
719
|
-
kind: '
|
|
720
|
-
domain: '
|
|
721
|
-
portEnv: '
|
|
725
|
+
id: 'remote-checkout',
|
|
726
|
+
directory: 'apps/remotes/remote-checkout',
|
|
727
|
+
packageSuffix: 'remote-checkout',
|
|
728
|
+
displayName: 'Checkout Remote',
|
|
729
|
+
kind: 'vertical',
|
|
730
|
+
domain: 'checkout',
|
|
731
|
+
portEnv: 'REMOTE_CHECKOUT_PORT',
|
|
722
732
|
port: 3023,
|
|
723
|
-
mfName: '
|
|
733
|
+
mfName: 'remoteCheckout',
|
|
724
734
|
exposes: {
|
|
725
|
-
'./
|
|
726
|
-
'./
|
|
735
|
+
'./AddToCart': './src/components/add-to-cart.tsx',
|
|
736
|
+
'./CartPage': './src/components/cart-page.tsx',
|
|
737
|
+
'./CheckoutPage': './src/components/checkout-page.tsx',
|
|
738
|
+
'./MiniCart': './src/components/mini-cart.tsx',
|
|
739
|
+
'./Route': './src/remote-entry.tsx',
|
|
740
|
+
'./ThanksPage': './src/components/thanks-page.tsx'
|
|
741
|
+
},
|
|
742
|
+
effectApi: {
|
|
743
|
+
stem: 'checkout',
|
|
744
|
+
prefix: '/checkout-api',
|
|
745
|
+
consumedBy: [
|
|
746
|
+
shellApp.id,
|
|
747
|
+
'remote-checkout'
|
|
748
|
+
]
|
|
727
749
|
},
|
|
728
750
|
ownership: {
|
|
729
|
-
team: '
|
|
730
|
-
slack: '#
|
|
731
|
-
pagerDuty: 'pd-
|
|
732
|
-
runbookRef: 'runbooks/wave2/remote-
|
|
733
|
-
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#remote-
|
|
751
|
+
team: 'tractor-checkout',
|
|
752
|
+
slack: '#tractor-checkout',
|
|
753
|
+
pagerDuty: 'pd-tractor-checkout',
|
|
754
|
+
runbookRef: 'runbooks/wave2/remote-checkout.md',
|
|
755
|
+
adrRef: 'docs/super-app-rfc-adr/wave2/reference-topology.md#remote-checkout',
|
|
734
756
|
blastRadius: {
|
|
735
|
-
tier: 'tier-
|
|
757
|
+
tier: 'tier-1-tractor-purchase',
|
|
736
758
|
references: [
|
|
737
|
-
'docs/super-app-rfc-adr/wave2/blast-radius.md#
|
|
738
|
-
'docs/super-app-rfc-adr/wave2/rollback.md#
|
|
759
|
+
'docs/super-app-rfc-adr/wave2/blast-radius.md#checkout',
|
|
760
|
+
'docs/super-app-rfc-adr/wave2/rollback.md#checkout-lkg'
|
|
739
761
|
]
|
|
740
762
|
}
|
|
741
763
|
}
|
|
@@ -1100,11 +1122,11 @@ function createRootPackageJson(scope, packageSource) {
|
|
|
1100
1122
|
type: 'module',
|
|
1101
1123
|
packageManager: `pnpm@${PNPM_VERSION}`,
|
|
1102
1124
|
scripts: {
|
|
1103
|
-
dev: `pnpm --parallel --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} --filter ${ultramodern_workspace_packageName(scope, 'remote-
|
|
1125
|
+
dev: `pnpm --parallel --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} --filter ${ultramodern_workspace_packageName(scope, 'remote-explore')} --filter ${ultramodern_workspace_packageName(scope, 'remote-decide')} --filter ${ultramodern_workspace_packageName(scope, 'remote-checkout')} dev`,
|
|
1104
1126
|
'dev:shell': `pnpm --filter ${ultramodern_workspace_packageName(scope, shellApp.packageSuffix)} dev`,
|
|
1105
|
-
'dev:
|
|
1106
|
-
'dev:
|
|
1107
|
-
'dev:
|
|
1127
|
+
'dev:explore': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-explore')} dev`,
|
|
1128
|
+
'dev:decide': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-decide')} dev`,
|
|
1129
|
+
'dev:checkout': `pnpm --filter ${ultramodern_workspace_packageName(scope, 'remote-checkout')} dev`,
|
|
1108
1130
|
build: 'pnpm -r --filter "./apps/remotes/**" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
|
|
1109
1131
|
format: 'oxfmt .',
|
|
1110
1132
|
'format:check': 'oxfmt --check .',
|
|
@@ -1112,6 +1134,8 @@ function createRootPackageJson(scope, packageSource) {
|
|
|
1112
1134
|
'lint:fix': 'oxlint . --fix',
|
|
1113
1135
|
typecheck: `pnpm -r --filter "@${scope}/*" typecheck`,
|
|
1114
1136
|
'cloudflare:build': 'pnpm -r --filter "./apps/remotes/**" run cloudflare:build && pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types',
|
|
1137
|
+
'cloudflare:deploy': 'pnpm -r --filter "./apps/remotes/**" run cloudflare:deploy && pnpm --filter "./apps/shell-super-app" run cloudflare:deploy',
|
|
1138
|
+
'cloudflare:proof': "node ./scripts/proof-cloudflare-version.mjs --out .codex/reports/cloudflare-version-proof/public-url-proof.json",
|
|
1115
1139
|
'skills:install': "node ./scripts/bootstrap-agent-skills.mjs",
|
|
1116
1140
|
'skills:check': "node ./scripts/bootstrap-agent-skills.mjs --check",
|
|
1117
1141
|
'agents:refs:install': "node ./scripts/setup-agent-reference-repos.mjs",
|
|
@@ -1158,13 +1182,61 @@ function remoteDependencyAlias(remote) {
|
|
|
1158
1182
|
function zephyrRemoteDependency(scope, remote) {
|
|
1159
1183
|
return `${ultramodern_workspace_packageName(scope, remote.packageSuffix)}@workspace:*`;
|
|
1160
1184
|
}
|
|
1185
|
+
function resolveRemoteRefs(app, remotes = remoteApps) {
|
|
1186
|
+
const remoteRefs = app.remoteRefs ?? [];
|
|
1187
|
+
return remoteRefs.map((remoteRef)=>remotes.find((remote)=>remote.id === remoteRef)).filter((remote)=>void 0 !== remote);
|
|
1188
|
+
}
|
|
1189
|
+
function createModuleFederationRemoteContracts(app, remotes = remoteApps) {
|
|
1190
|
+
return resolveRemoteRefs(app, remotes).map((remote)=>({
|
|
1191
|
+
id: remote.id,
|
|
1192
|
+
alias: remoteDependencyAlias(remote),
|
|
1193
|
+
name: remote.mfName,
|
|
1194
|
+
manifestEnv: createRemoteManifestEnv(remote),
|
|
1195
|
+
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
|
|
1196
|
+
}));
|
|
1197
|
+
}
|
|
1161
1198
|
function createZephyrDependencies(scope, app, remotes = remoteApps) {
|
|
1162
|
-
if (
|
|
1163
|
-
return Object.fromEntries(remotes.map((remote)=>[
|
|
1199
|
+
if (!app.remoteRefs?.length) return {};
|
|
1200
|
+
return Object.fromEntries(resolveRemoteRefs(app, remotes).map((remote)=>[
|
|
1164
1201
|
remoteDependencyAlias(remote),
|
|
1165
1202
|
zephyrRemoteDependency(scope, remote)
|
|
1166
1203
|
]));
|
|
1167
1204
|
}
|
|
1205
|
+
function createCloudflareWorkerName(scope, app) {
|
|
1206
|
+
return toKebabCase(`${scope}-${app.packageSuffix}`).slice(0, 63);
|
|
1207
|
+
}
|
|
1208
|
+
function createCloudflarePublicUrlEnv(app) {
|
|
1209
|
+
return `ULTRAMODERN_PUBLIC_URL_${toEnvSegment(app.id)}`;
|
|
1210
|
+
}
|
|
1211
|
+
function createCloudflareProofRoute(app) {
|
|
1212
|
+
const languageRoutes = createLocalisedUrlsMap(app);
|
|
1213
|
+
const firstCanonicalPath = Object.keys(languageRoutes)[0];
|
|
1214
|
+
const localizedPath = firstCanonicalPath && isRecord(languageRoutes[firstCanonicalPath]) ? languageRoutes[firstCanonicalPath].en : void 0;
|
|
1215
|
+
return {
|
|
1216
|
+
ssr: localizedPath ?? '/en',
|
|
1217
|
+
mfManifest: '/mf-manifest.json',
|
|
1218
|
+
locale: `/locales/en/${appI18nNamespace(app)}.json`,
|
|
1219
|
+
...appHasEffectApi(app) ? {
|
|
1220
|
+
effectReadiness: `${effectApiPrefix(app)}/effect/${effectApiStem(app)}/readiness`
|
|
1221
|
+
} : {}
|
|
1222
|
+
};
|
|
1223
|
+
}
|
|
1224
|
+
function createCloudflareDeployContract(scope, app) {
|
|
1225
|
+
return {
|
|
1226
|
+
target: 'cloudflare',
|
|
1227
|
+
workerName: createCloudflareWorkerName(scope, app),
|
|
1228
|
+
publicUrlEnv: createCloudflarePublicUrlEnv(app),
|
|
1229
|
+
compatibilityFlags: [
|
|
1230
|
+
'nodejs_compat'
|
|
1231
|
+
],
|
|
1232
|
+
assetsBinding: 'ASSETS',
|
|
1233
|
+
routes: createCloudflareProofRoute(app),
|
|
1234
|
+
evidence: {
|
|
1235
|
+
proofScript: "scripts/proof-cloudflare-version.mjs",
|
|
1236
|
+
reportDefault: '.codex/reports/cloudflare-version-proof/public-url-proof.json'
|
|
1237
|
+
}
|
|
1238
|
+
};
|
|
1239
|
+
}
|
|
1168
1240
|
function createTsConfigBase() {
|
|
1169
1241
|
return {
|
|
1170
1242
|
compilerOptions: {
|
|
@@ -1234,7 +1306,9 @@ function createAppPackage(scope, app, packageSource, enableTailwind) {
|
|
|
1234
1306
|
dev: 'modern dev',
|
|
1235
1307
|
build: app.exposes ? `modern build && node ${relativeRootFor(app.directory)}/scripts/assert-mf-types.mjs` : 'modern build',
|
|
1236
1308
|
'cloudflare:build': 'MODERNJS_DEPLOY=cloudflare modern build && MODERNJS_DEPLOY=cloudflare modern deploy',
|
|
1309
|
+
'cloudflare:deploy': 'MODERNJS_DEPLOY=cloudflare modern deploy',
|
|
1237
1310
|
'cloudflare:preview': 'MODERNJS_DEPLOY=cloudflare modern build && MODERNJS_DEPLOY=cloudflare modern deploy && wrangler dev --config .output/wrangler.json',
|
|
1311
|
+
'cloudflare:proof': `node ${relativeRootFor(app.directory)}/scripts/proof-cloudflare-version.mjs --app ${app.id}`,
|
|
1238
1312
|
serve: 'modern serve',
|
|
1239
1313
|
typecheck: effectTsgoTypecheckCommand
|
|
1240
1314
|
},
|
|
@@ -1255,6 +1329,9 @@ function createAppPackage(scope, app, packageSource, enableTailwind) {
|
|
|
1255
1329
|
'./effect/client': `./src/effect/${app.effectApi.stem}-client.ts`,
|
|
1256
1330
|
'./shared/effect/api': './shared/effect/api.ts'
|
|
1257
1331
|
};
|
|
1332
|
+
else if ('shell' === app.kind) packageJson.exports = {
|
|
1333
|
+
'./effect/clients': './src/effect/recommendations-client.ts'
|
|
1334
|
+
};
|
|
1258
1335
|
return packageJson;
|
|
1259
1336
|
}
|
|
1260
1337
|
function createServicePackage(scope, packageSource, enableTailwind, service = effectService) {
|
|
@@ -1315,12 +1392,16 @@ function createSharedPackage(scope, id, description, packageSource) {
|
|
|
1315
1392
|
"@typescript/native-preview": TYPESCRIPT_NATIVE_PREVIEW_VERSION
|
|
1316
1393
|
}
|
|
1317
1394
|
};
|
|
1395
|
+
if ('shared-design-tokens' === id) packageJson.exports = {
|
|
1396
|
+
...packageJson.exports,
|
|
1397
|
+
'./tokens.css': './src/tokens.css'
|
|
1398
|
+
};
|
|
1318
1399
|
if ('shared-effect-api' === id) packageJson.dependencies = {
|
|
1319
1400
|
'@modern-js/plugin-bff': modernPackageSpecifier('@modern-js/plugin-bff', packageSource)
|
|
1320
1401
|
};
|
|
1321
1402
|
return packageJson;
|
|
1322
1403
|
}
|
|
1323
|
-
function createAppModernConfig(app) {
|
|
1404
|
+
function createAppModernConfig(scope, app) {
|
|
1324
1405
|
const bffImport = appHasEffectApi(app) ? "import { bffPlugin } from '@modern-js/plugin-bff';\n" : '';
|
|
1325
1406
|
const bffConfig = appHasEffectApi(app) ? ` bff: {
|
|
1326
1407
|
effect: {
|
|
@@ -1343,6 +1424,7 @@ ${bffImport}import { i18nPlugin } from '@modern-js/plugin-i18n';
|
|
|
1343
1424
|
import { tanstackRouterPlugin } from '@modern-js/plugin-tanstack';
|
|
1344
1425
|
import { moduleFederationPlugin } from '@module-federation/modern-js-v3';
|
|
1345
1426
|
import { withZephyr as withZephyrRspack } from 'zephyr-rspack-plugin';
|
|
1427
|
+
import { ultramodernLocalisedUrls } from './src/routes/ultramodern-route-metadata';
|
|
1346
1428
|
|
|
1347
1429
|
type ZephyrRspackConfig = Parameters<ReturnType<typeof withZephyrRspack>>[0];
|
|
1348
1430
|
|
|
@@ -1361,6 +1443,7 @@ const zephyrRspackPlugin = () => ({
|
|
|
1361
1443
|
});
|
|
1362
1444
|
|
|
1363
1445
|
const appId = '${app.id}';
|
|
1446
|
+
const cloudflareWorkerName = '${createCloudflareWorkerName(scope, app)}';
|
|
1364
1447
|
const port = Number(process.env['${app.portEnv}'] ?? ${app.port});
|
|
1365
1448
|
const configuredSiteUrl = process.env['MODERN_PUBLIC_SITE_URL'];
|
|
1366
1449
|
const hasConfiguredSiteUrl =
|
|
@@ -1403,13 +1486,14 @@ ${bffConfig} output: {
|
|
|
1403
1486
|
tanstackRouterPlugin(),
|
|
1404
1487
|
i18nPlugin({
|
|
1405
1488
|
backend: {
|
|
1406
|
-
enabled:
|
|
1489
|
+
enabled: true,
|
|
1407
1490
|
},
|
|
1408
1491
|
reactI18next: false,
|
|
1409
1492
|
localeDetection: {
|
|
1410
1493
|
fallbackLanguage: 'en',
|
|
1411
1494
|
languages: ['en', 'cs'],
|
|
1412
1495
|
localePathRedirect: true,
|
|
1496
|
+
localisedUrls: ultramodernLocalisedUrls as Record<string, Record<string, string>>,
|
|
1413
1497
|
ignoreRedirectRoutes: [
|
|
1414
1498
|
'/@mf-types',
|
|
1415
1499
|
'/bundles',
|
|
@@ -1442,6 +1526,7 @@ ${bffPluginEntry} moduleFederationPlugin(),
|
|
|
1442
1526
|
deploy: {
|
|
1443
1527
|
target: 'cloudflare',
|
|
1444
1528
|
worker: {
|
|
1529
|
+
name: cloudflareWorkerName,
|
|
1445
1530
|
ssr: true,
|
|
1446
1531
|
},
|
|
1447
1532
|
},
|
|
@@ -1510,13 +1595,24 @@ ${entries.map(([key, entryValue])=>` '${key}': '${entryValue}',`).join('\n')}
|
|
|
1510
1595
|
function createRemoteManifestEnv(remote) {
|
|
1511
1596
|
return `REMOTE_${toEnvSegment(remote.domain ?? remote.id)}_MF_MANIFEST`;
|
|
1512
1597
|
}
|
|
1513
|
-
function
|
|
1514
|
-
const remoteEntries = remotes.map((remote)=>{
|
|
1598
|
+
function createModuleFederationRemotesConfig(app, remotes = remoteApps) {
|
|
1599
|
+
const remoteEntries = resolveRemoteRefs(app, remotes).map((remote)=>{
|
|
1515
1600
|
const key = remoteDependencyAlias(remote);
|
|
1516
1601
|
return ` ${key}:
|
|
1517
1602
|
process.env['${createRemoteManifestEnv(remote)}'] ??
|
|
1518
1603
|
'${remote.mfName}@http://localhost:${remote.port}/mf-manifest.json',`;
|
|
1519
1604
|
}).join('\n');
|
|
1605
|
+
if (!remoteEntries) return '';
|
|
1606
|
+
return ` remotes: {
|
|
1607
|
+
${remoteEntries}
|
|
1608
|
+
},
|
|
1609
|
+
`;
|
|
1610
|
+
}
|
|
1611
|
+
function createShellModuleFederationConfig(remotes = remoteApps) {
|
|
1612
|
+
const shellHost = {
|
|
1613
|
+
...shellApp,
|
|
1614
|
+
remoteRefs: remotes.map((remote)=>remote.id)
|
|
1615
|
+
};
|
|
1520
1616
|
return `// @effect-diagnostics nodeBuiltinImport:off processEnv:off
|
|
1521
1617
|
import { createRequire } from 'node:module';
|
|
1522
1618
|
import { createModuleFederationConfig } from '@module-federation/modern-js-v3';
|
|
@@ -1540,10 +1636,7 @@ export default createModuleFederationConfig({
|
|
|
1540
1636
|
},
|
|
1541
1637
|
filename: 'remoteEntry.js',
|
|
1542
1638
|
name: '${shellApp.mfName}',
|
|
1543
|
-
|
|
1544
|
-
${remoteEntries}
|
|
1545
|
-
},
|
|
1546
|
-
${createSharedModuleFederationConfig()},
|
|
1639
|
+
${createModuleFederationRemotesConfig(shellHost, remotes)}${createSharedModuleFederationConfig()},
|
|
1547
1640
|
});
|
|
1548
1641
|
`;
|
|
1549
1642
|
}
|
|
@@ -1570,7 +1663,7 @@ export const ultramodernApiMarker = {
|
|
|
1570
1663
|
} as const;
|
|
1571
1664
|
`;
|
|
1572
1665
|
}
|
|
1573
|
-
function createRemoteModuleFederationConfig(app) {
|
|
1666
|
+
function createRemoteModuleFederationConfig(app, remotes = remoteApps) {
|
|
1574
1667
|
const exposes = formatTsObjectLiteral(app.exposes ?? {});
|
|
1575
1668
|
return `// @effect-diagnostics nodeBuiltinImport:off
|
|
1576
1669
|
import { createRequire } from 'node:module';
|
|
@@ -1596,12 +1689,248 @@ export default createModuleFederationConfig({
|
|
|
1596
1689
|
exposes: ${exposes},
|
|
1597
1690
|
filename: 'remoteEntry.js',
|
|
1598
1691
|
name: '${app.mfName}',
|
|
1599
|
-
${createSharedModuleFederationConfig()},
|
|
1692
|
+
${createModuleFederationRemotesConfig(app, remotes)}${createSharedModuleFederationConfig()},
|
|
1600
1693
|
});
|
|
1601
1694
|
`;
|
|
1602
1695
|
}
|
|
1603
|
-
function
|
|
1604
|
-
return
|
|
1696
|
+
function appI18nNamespace(app) {
|
|
1697
|
+
return 'shell' === app.kind ? 'shell' : app.domain ?? app.id;
|
|
1698
|
+
}
|
|
1699
|
+
function createRouteOwnedI18nPaths(app) {
|
|
1700
|
+
const namespace = appI18nNamespace(app);
|
|
1701
|
+
const base = {
|
|
1702
|
+
mfBoundaryId: app.mfName,
|
|
1703
|
+
namespace,
|
|
1704
|
+
ownerAppId: app.id
|
|
1705
|
+
};
|
|
1706
|
+
if ('shell' === app.kind) return [
|
|
1707
|
+
{
|
|
1708
|
+
...base,
|
|
1709
|
+
canonicalPath: '/',
|
|
1710
|
+
id: 'shell-home',
|
|
1711
|
+
localisedPaths: {
|
|
1712
|
+
cs: '/',
|
|
1713
|
+
en: '/'
|
|
1714
|
+
},
|
|
1715
|
+
titleKey: 'shell.title'
|
|
1716
|
+
}
|
|
1717
|
+
];
|
|
1718
|
+
if ('explore' === app.domain) return [
|
|
1719
|
+
{
|
|
1720
|
+
...base,
|
|
1721
|
+
canonicalPath: '/',
|
|
1722
|
+
id: 'explore-home',
|
|
1723
|
+
localisedPaths: {
|
|
1724
|
+
cs: '/',
|
|
1725
|
+
en: '/'
|
|
1726
|
+
},
|
|
1727
|
+
titleKey: 'explore.title'
|
|
1728
|
+
},
|
|
1729
|
+
{
|
|
1730
|
+
...base,
|
|
1731
|
+
canonicalPath: '/tractors',
|
|
1732
|
+
id: 'explore-listing',
|
|
1733
|
+
localisedPaths: {
|
|
1734
|
+
cs: '/traktory',
|
|
1735
|
+
en: '/tractors'
|
|
1736
|
+
},
|
|
1737
|
+
titleKey: 'explore.routes.listing'
|
|
1738
|
+
},
|
|
1739
|
+
{
|
|
1740
|
+
...base,
|
|
1741
|
+
canonicalPath: '/stores',
|
|
1742
|
+
id: 'explore-store-picker',
|
|
1743
|
+
localisedPaths: {
|
|
1744
|
+
cs: '/prodejci',
|
|
1745
|
+
en: '/stores'
|
|
1746
|
+
},
|
|
1747
|
+
titleKey: 'explore.routes.storePicker'
|
|
1748
|
+
},
|
|
1749
|
+
{
|
|
1750
|
+
...base,
|
|
1751
|
+
canonicalPath: '/unavailable',
|
|
1752
|
+
id: 'explore-unavailable',
|
|
1753
|
+
localisedPaths: {
|
|
1754
|
+
cs: '/nedostupne',
|
|
1755
|
+
en: '/unavailable'
|
|
1756
|
+
},
|
|
1757
|
+
titleKey: 'explore.routes.unavailable'
|
|
1758
|
+
}
|
|
1759
|
+
];
|
|
1760
|
+
if ('decide' === app.domain) return [
|
|
1761
|
+
{
|
|
1762
|
+
...base,
|
|
1763
|
+
canonicalPath: '/',
|
|
1764
|
+
id: 'decide-home',
|
|
1765
|
+
localisedPaths: {
|
|
1766
|
+
cs: '/',
|
|
1767
|
+
en: '/'
|
|
1768
|
+
},
|
|
1769
|
+
titleKey: 'decide.title'
|
|
1770
|
+
},
|
|
1771
|
+
{
|
|
1772
|
+
...base,
|
|
1773
|
+
canonicalPath: '/tractors',
|
|
1774
|
+
id: 'decide-listing-parent',
|
|
1775
|
+
localisedPaths: {
|
|
1776
|
+
cs: '/traktory',
|
|
1777
|
+
en: '/tractors'
|
|
1778
|
+
},
|
|
1779
|
+
titleKey: 'decide.routes.listing'
|
|
1780
|
+
},
|
|
1781
|
+
{
|
|
1782
|
+
...base,
|
|
1783
|
+
canonicalPath: '/tractors/:slug',
|
|
1784
|
+
id: 'decide-product-detail',
|
|
1785
|
+
localisedPaths: {
|
|
1786
|
+
cs: '/traktory/:slug',
|
|
1787
|
+
en: '/tractors/:slug'
|
|
1788
|
+
},
|
|
1789
|
+
titleKey: 'decide.routes.productDetail'
|
|
1790
|
+
},
|
|
1791
|
+
{
|
|
1792
|
+
...base,
|
|
1793
|
+
canonicalPath: '/unavailable',
|
|
1794
|
+
id: 'decide-unavailable',
|
|
1795
|
+
localisedPaths: {
|
|
1796
|
+
cs: '/nedostupne',
|
|
1797
|
+
en: '/unavailable'
|
|
1798
|
+
},
|
|
1799
|
+
titleKey: 'decide.routes.unavailable'
|
|
1800
|
+
}
|
|
1801
|
+
];
|
|
1802
|
+
if ('checkout' === app.domain) return [
|
|
1803
|
+
{
|
|
1804
|
+
...base,
|
|
1805
|
+
canonicalPath: '/',
|
|
1806
|
+
id: 'checkout-home',
|
|
1807
|
+
localisedPaths: {
|
|
1808
|
+
cs: '/',
|
|
1809
|
+
en: '/'
|
|
1810
|
+
},
|
|
1811
|
+
titleKey: 'checkout.title'
|
|
1812
|
+
},
|
|
1813
|
+
{
|
|
1814
|
+
...base,
|
|
1815
|
+
canonicalPath: '/cart',
|
|
1816
|
+
id: 'checkout-cart',
|
|
1817
|
+
localisedPaths: {
|
|
1818
|
+
cs: '/kosik',
|
|
1819
|
+
en: '/cart'
|
|
1820
|
+
},
|
|
1821
|
+
titleKey: 'checkout.routes.cart'
|
|
1822
|
+
},
|
|
1823
|
+
{
|
|
1824
|
+
...base,
|
|
1825
|
+
canonicalPath: '/checkout',
|
|
1826
|
+
id: 'checkout-start',
|
|
1827
|
+
localisedPaths: {
|
|
1828
|
+
cs: '/pokladna',
|
|
1829
|
+
en: '/checkout'
|
|
1830
|
+
},
|
|
1831
|
+
titleKey: 'checkout.routes.checkout'
|
|
1832
|
+
},
|
|
1833
|
+
{
|
|
1834
|
+
...base,
|
|
1835
|
+
canonicalPath: '/checkout/thank-you',
|
|
1836
|
+
id: 'checkout-thank-you-parent',
|
|
1837
|
+
localisedPaths: {
|
|
1838
|
+
cs: '/pokladna/dekujeme',
|
|
1839
|
+
en: '/checkout/thank-you'
|
|
1840
|
+
},
|
|
1841
|
+
titleKey: 'checkout.routes.thankYou'
|
|
1842
|
+
},
|
|
1843
|
+
{
|
|
1844
|
+
...base,
|
|
1845
|
+
canonicalPath: '/checkout/thank-you/:orderId?',
|
|
1846
|
+
id: 'checkout-thank-you',
|
|
1847
|
+
localisedPaths: {
|
|
1848
|
+
cs: '/pokladna/dekujeme/:orderId?',
|
|
1849
|
+
en: '/checkout/thank-you/:orderId?'
|
|
1850
|
+
},
|
|
1851
|
+
titleKey: 'checkout.routes.thankYou'
|
|
1852
|
+
},
|
|
1853
|
+
{
|
|
1854
|
+
...base,
|
|
1855
|
+
canonicalPath: '/unavailable',
|
|
1856
|
+
id: 'checkout-unavailable',
|
|
1857
|
+
localisedPaths: {
|
|
1858
|
+
cs: '/nedostupne',
|
|
1859
|
+
en: '/unavailable'
|
|
1860
|
+
},
|
|
1861
|
+
titleKey: 'checkout.routes.unavailable'
|
|
1862
|
+
}
|
|
1863
|
+
];
|
|
1864
|
+
return [
|
|
1865
|
+
{
|
|
1866
|
+
...base,
|
|
1867
|
+
canonicalPath: '/',
|
|
1868
|
+
id: `${app.id}-home`,
|
|
1869
|
+
localisedPaths: {
|
|
1870
|
+
cs: '/',
|
|
1871
|
+
en: '/'
|
|
1872
|
+
},
|
|
1873
|
+
titleKey: `${namespace}.title`
|
|
1874
|
+
}
|
|
1875
|
+
];
|
|
1876
|
+
}
|
|
1877
|
+
function createLocalisedUrlsMap(app) {
|
|
1878
|
+
return Object.fromEntries(createRouteOwnedI18nPaths(app).filter((route)=>'/' !== route.canonicalPath).map((route)=>[
|
|
1879
|
+
route.canonicalPath,
|
|
1880
|
+
route.localisedPaths
|
|
1881
|
+
]));
|
|
1882
|
+
}
|
|
1883
|
+
function createRouteMetadataModule(app) {
|
|
1884
|
+
const routes = createRouteOwnedI18nPaths(app);
|
|
1885
|
+
const localisedUrls = createLocalisedUrlsMap(app);
|
|
1886
|
+
const namespace = appI18nNamespace(app);
|
|
1887
|
+
return `export const ultramodernRouteNamespace = '${namespace}' as const;
|
|
1888
|
+
|
|
1889
|
+
export const ultramodernRouteMetadata = ${JSON.stringify(routes, null, 2)} as const;
|
|
1890
|
+
|
|
1891
|
+
export const ultramodernLocalisedUrls = ${JSON.stringify(localisedUrls, null, 2)} as const;
|
|
1892
|
+
|
|
1893
|
+
export const ultramodernRouteConfig = {
|
|
1894
|
+
source: 'route-owned',
|
|
1895
|
+
namespace: ultramodernRouteNamespace,
|
|
1896
|
+
localisedUrls: ultramodernLocalisedUrls,
|
|
1897
|
+
routes: ultramodernRouteMetadata,
|
|
1898
|
+
} as const;
|
|
1899
|
+
`;
|
|
1900
|
+
}
|
|
1901
|
+
function routeSegmentToDirectory(segment) {
|
|
1902
|
+
if (segment.startsWith(':')) {
|
|
1903
|
+
const name = segment.slice(1).replace(/\?$/u, '');
|
|
1904
|
+
return segment.endsWith('?') ? `[${name}$]` : `[${name}]`;
|
|
1905
|
+
}
|
|
1906
|
+
return segment;
|
|
1907
|
+
}
|
|
1908
|
+
function createRoutePageFilePath(app, canonicalPath) {
|
|
1909
|
+
const segments = canonicalPath.split('/').filter(Boolean).map(routeSegmentToDirectory);
|
|
1910
|
+
return `${app.directory}/src/routes/[lang]/${[
|
|
1911
|
+
...segments,
|
|
1912
|
+
'page.tsx'
|
|
1913
|
+
].join('/')}`;
|
|
1914
|
+
}
|
|
1915
|
+
function createRouteAliasPage(canonicalPath) {
|
|
1916
|
+
const depth = canonicalPath.split('/').filter(Boolean).length;
|
|
1917
|
+
const rootPageImport = `${'../'.repeat(depth)}page`;
|
|
1918
|
+
return `export { default } from '${rootPageImport}';
|
|
1919
|
+
`;
|
|
1920
|
+
}
|
|
1921
|
+
function createAppEnvDts(app, remotes = remoteApps) {
|
|
1922
|
+
const remoteModuleDeclarations = resolveRemoteRefs(app, remotes).flatMap((remote)=>Object.keys(remote.exposes ?? {}).filter((expose)=>'./Route' !== expose).map((expose)=>{
|
|
1923
|
+
const moduleName = `${remoteDependencyAlias(remote)}/${expose.replace(/^\.\//u, '')}`;
|
|
1924
|
+
return `declare module '${moduleName}' {
|
|
1925
|
+
const Component: import('react').ComponentType<Record<string, never>>;
|
|
1926
|
+
export default Component;
|
|
1927
|
+
}
|
|
1928
|
+
`;
|
|
1929
|
+
})).join('\n');
|
|
1930
|
+
return `/// <reference types='@modern-js/app-tools/types' />
|
|
1931
|
+
|
|
1932
|
+
declare const ULTRAMODERN_SITE_URL: string;
|
|
1933
|
+
${remoteModuleDeclarations ? `\n${remoteModuleDeclarations}` : ''}`;
|
|
1605
1934
|
}
|
|
1606
1935
|
function createServiceModernConfigFor(service = effectService) {
|
|
1607
1936
|
return `// @effect-diagnostics processEnv:off
|
|
@@ -1639,16 +1968,20 @@ export default defineConfig(
|
|
|
1639
1968
|
`;
|
|
1640
1969
|
}
|
|
1641
1970
|
function createAppRuntimeConfig(app) {
|
|
1971
|
+
const namespace = appI18nNamespace(app);
|
|
1642
1972
|
const resources = {
|
|
1643
1973
|
cs: {
|
|
1974
|
+
[namespace]: createAppLocaleMessages(app, 'cs'),
|
|
1644
1975
|
translation: createAppLocaleMessages(app, 'cs')
|
|
1645
1976
|
},
|
|
1646
1977
|
en: {
|
|
1978
|
+
[namespace]: createAppLocaleMessages(app, 'en'),
|
|
1647
1979
|
translation: createAppLocaleMessages(app, 'en')
|
|
1648
1980
|
}
|
|
1649
1981
|
};
|
|
1650
1982
|
return `import { defineRuntimeConfig } from '@modern-js/runtime';
|
|
1651
1983
|
import { createInstance } from 'i18next';
|
|
1984
|
+
import { ultramodernRouteNamespace } from './routes/ultramodern-route-metadata';
|
|
1652
1985
|
|
|
1653
1986
|
const i18nInstance = createInstance();
|
|
1654
1987
|
|
|
@@ -1656,12 +1989,12 @@ export default defineRuntimeConfig({
|
|
|
1656
1989
|
i18n: {
|
|
1657
1990
|
i18nInstance,
|
|
1658
1991
|
initOptions: {
|
|
1659
|
-
defaultNS:
|
|
1992
|
+
defaultNS: ultramodernRouteNamespace,
|
|
1660
1993
|
fallbackLng: 'en',
|
|
1661
1994
|
interpolation: {
|
|
1662
1995
|
escapeValue: false,
|
|
1663
1996
|
},
|
|
1664
|
-
ns: ['translation'],
|
|
1997
|
+
ns: [ultramodernRouteNamespace, 'translation'],
|
|
1665
1998
|
resources: ${JSON.stringify(resources, null, 8).split('\n').join('\n ')},
|
|
1666
1999
|
supportedLngs: ['en', 'cs'],
|
|
1667
2000
|
},
|
|
@@ -1672,10 +2005,16 @@ export default defineRuntimeConfig({
|
|
|
1672
2005
|
});
|
|
1673
2006
|
`;
|
|
1674
2007
|
}
|
|
1675
|
-
function
|
|
1676
|
-
return
|
|
1677
|
-
|
|
1678
|
-
|
|
2008
|
+
function createCssTokenImport(scope) {
|
|
2009
|
+
return `@import '${ultramodern_workspace_packageName(scope, 'shared-design-tokens')}/tokens.css';\n`;
|
|
2010
|
+
}
|
|
2011
|
+
function createShellStyles(enableTailwind, scope) {
|
|
2012
|
+
return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}
|
|
2013
|
+
|
|
2014
|
+
@layer ultramodern-shell-base {
|
|
2015
|
+
:root {
|
|
2016
|
+
color: var(--um-color-foreground);
|
|
2017
|
+
background: var(--um-color-canvas);
|
|
1679
2018
|
font-family:
|
|
1680
2019
|
Geist,
|
|
1681
2020
|
Inter,
|
|
@@ -1703,9 +2042,52 @@ nav {
|
|
|
1703
2042
|
}
|
|
1704
2043
|
|
|
1705
2044
|
a {
|
|
1706
|
-
color:
|
|
2045
|
+
color: var(--um-color-link);
|
|
2046
|
+
}
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
@layer ultramodern-shell-overlay {
|
|
2050
|
+
.boundary-overlay {
|
|
2051
|
+
inset: 0;
|
|
2052
|
+
pointer-events: none;
|
|
2053
|
+
position: fixed;
|
|
2054
|
+
z-index: 70;
|
|
2055
|
+
}
|
|
2056
|
+
|
|
2057
|
+
.boundary-overlay__box {
|
|
2058
|
+
border: 0.0625rem solid var(--boundary-color);
|
|
2059
|
+
border-radius: 0.55rem;
|
|
2060
|
+
box-shadow:
|
|
2061
|
+
0 0 0 0.0625rem rgba(255, 255, 255, 0.72),
|
|
2062
|
+
0 0.35rem 1.25rem color-mix(in srgb, var(--boundary-color) 20%, transparent);
|
|
2063
|
+
position: fixed;
|
|
2064
|
+
}
|
|
2065
|
+
|
|
2066
|
+
.boundary-overlay__label {
|
|
2067
|
+
background: color-mix(in srgb, var(--boundary-color) 88%, white);
|
|
2068
|
+
border-radius: 999px;
|
|
2069
|
+
color: #0b0a08;
|
|
2070
|
+
font-size: 0.7rem;
|
|
2071
|
+
font-weight: 850;
|
|
2072
|
+
line-height: 1;
|
|
2073
|
+
padding: 0.3rem 0.55rem;
|
|
2074
|
+
position: absolute;
|
|
2075
|
+
right: 0.35rem;
|
|
2076
|
+
top: 0.35rem;
|
|
2077
|
+
white-space: nowrap;
|
|
1707
2078
|
}
|
|
1708
2079
|
|
|
2080
|
+
.boundary-overlay__box[data-label-placement="above"] .boundary-overlay__label {
|
|
2081
|
+
bottom: calc(100% + 0.25rem);
|
|
2082
|
+
top: auto;
|
|
2083
|
+
}
|
|
2084
|
+
}
|
|
2085
|
+
`;
|
|
2086
|
+
}
|
|
2087
|
+
function createRemoteStyles(enableTailwind, scope, app) {
|
|
2088
|
+
if ('commerce' === app.domain) return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}
|
|
2089
|
+
|
|
2090
|
+
@layer ultramodern-remote-${app.domain} {
|
|
1709
2091
|
.commerce-shell {
|
|
1710
2092
|
background: #f1eadc;
|
|
1711
2093
|
color: #0b0a08;
|
|
@@ -1933,41 +2315,6 @@ a {
|
|
|
1933
2315
|
width: 1rem;
|
|
1934
2316
|
}
|
|
1935
2317
|
|
|
1936
|
-
.boundary-overlay {
|
|
1937
|
-
inset: 0;
|
|
1938
|
-
pointer-events: none;
|
|
1939
|
-
position: fixed;
|
|
1940
|
-
z-index: 70;
|
|
1941
|
-
}
|
|
1942
|
-
|
|
1943
|
-
.boundary-overlay__box {
|
|
1944
|
-
border: 0.0625rem solid var(--boundary-color);
|
|
1945
|
-
border-radius: 0.55rem;
|
|
1946
|
-
box-shadow:
|
|
1947
|
-
0 0 0 0.0625rem rgba(255, 255, 255, 0.72),
|
|
1948
|
-
0 0.35rem 1.25rem color-mix(in srgb, var(--boundary-color) 20%, transparent);
|
|
1949
|
-
position: fixed;
|
|
1950
|
-
}
|
|
1951
|
-
|
|
1952
|
-
.boundary-overlay__label {
|
|
1953
|
-
background: color-mix(in srgb, var(--boundary-color) 88%, white);
|
|
1954
|
-
border-radius: 999px;
|
|
1955
|
-
color: #0b0a08;
|
|
1956
|
-
font-size: 0.7rem;
|
|
1957
|
-
font-weight: 850;
|
|
1958
|
-
line-height: 1;
|
|
1959
|
-
padding: 0.3rem 0.55rem;
|
|
1960
|
-
position: absolute;
|
|
1961
|
-
right: 0.35rem;
|
|
1962
|
-
top: 0.35rem;
|
|
1963
|
-
white-space: nowrap;
|
|
1964
|
-
}
|
|
1965
|
-
|
|
1966
|
-
.boundary-overlay__box[data-label-placement="above"] .boundary-overlay__label {
|
|
1967
|
-
bottom: calc(100% + 0.25rem);
|
|
1968
|
-
top: auto;
|
|
1969
|
-
}
|
|
1970
|
-
|
|
1971
2318
|
@media (max-width: 860px) {
|
|
1972
2319
|
.commerce-header,
|
|
1973
2320
|
.commerce-footer,
|
|
@@ -1987,8 +2334,78 @@ a {
|
|
|
1987
2334
|
min-height: 20rem;
|
|
1988
2335
|
}
|
|
1989
2336
|
}
|
|
2337
|
+
}
|
|
2338
|
+
`;
|
|
2339
|
+
return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}
|
|
2340
|
+
|
|
2341
|
+
@layer ultramodern-remote-${app.domain ?? app.id} {
|
|
2342
|
+
[data-app-id="${app.id}"] {
|
|
2343
|
+
color: var(--um-color-foreground);
|
|
2344
|
+
background: var(--um-color-surface);
|
|
2345
|
+
font-family:
|
|
2346
|
+
Geist,
|
|
2347
|
+
Inter,
|
|
2348
|
+
ui-sans-serif,
|
|
2349
|
+
system-ui,
|
|
2350
|
+
-apple-system,
|
|
2351
|
+
BlinkMacSystemFont,
|
|
2352
|
+
"Segoe UI",
|
|
2353
|
+
sans-serif;
|
|
2354
|
+
min-height: 100vh;
|
|
2355
|
+
}
|
|
2356
|
+
|
|
2357
|
+
[data-app-id="${app.id}"] main {
|
|
2358
|
+
min-height: 100vh;
|
|
2359
|
+
padding: 2rem;
|
|
2360
|
+
}
|
|
2361
|
+
|
|
2362
|
+
[data-app-id="${app.id}"] nav {
|
|
2363
|
+
display: flex;
|
|
2364
|
+
gap: 0.75rem;
|
|
2365
|
+
margin-bottom: 2rem;
|
|
2366
|
+
}
|
|
2367
|
+
|
|
2368
|
+
[data-app-id="${app.id}"] a {
|
|
2369
|
+
color: var(--um-color-link);
|
|
2370
|
+
}
|
|
2371
|
+
|
|
2372
|
+
[data-mf-remote="${app.id}"] {
|
|
2373
|
+
border: 0.0625rem solid color-mix(in srgb, var(--um-color-accent) 30%, transparent);
|
|
2374
|
+
border-radius: 0.5rem;
|
|
2375
|
+
padding: 1rem;
|
|
2376
|
+
}
|
|
2377
|
+
}
|
|
2378
|
+
`;
|
|
2379
|
+
}
|
|
2380
|
+
function createServiceStyles(enableTailwind, scope, service) {
|
|
2381
|
+
return `${enableTailwind ? "@import 'tailwindcss';\n" : ''}${createCssTokenImport(scope)}
|
|
2382
|
+
|
|
2383
|
+
@layer ultramodern-effect-service {
|
|
2384
|
+
[data-app-id="${service.id}"] {
|
|
2385
|
+
color: var(--um-color-foreground);
|
|
2386
|
+
background: var(--um-color-surface);
|
|
2387
|
+
font-family:
|
|
2388
|
+
Geist,
|
|
2389
|
+
Inter,
|
|
2390
|
+
ui-sans-serif,
|
|
2391
|
+
system-ui,
|
|
2392
|
+
-apple-system,
|
|
2393
|
+
BlinkMacSystemFont,
|
|
2394
|
+
"Segoe UI",
|
|
2395
|
+
sans-serif;
|
|
2396
|
+
min-height: 100vh;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
2399
|
+
[data-app-id="${service.id}"] main {
|
|
2400
|
+
min-height: 100vh;
|
|
2401
|
+
padding: 2rem;
|
|
2402
|
+
}
|
|
2403
|
+
}
|
|
1990
2404
|
`;
|
|
1991
2405
|
}
|
|
2406
|
+
function createAppStyles(enableTailwind, scope, app) {
|
|
2407
|
+
return 'shell' === app.kind ? createShellStyles(enableTailwind, scope) : createRemoteStyles(enableTailwind, scope, app);
|
|
2408
|
+
}
|
|
1992
2409
|
function createPostcssConfig() {
|
|
1993
2410
|
return `export default {
|
|
1994
2411
|
plugins: {
|
|
@@ -2010,48 +2427,168 @@ function createLocalizedHeadComponent() {
|
|
|
2010
2427
|
const supportedLanguages = ['en', 'cs'] as const;
|
|
2011
2428
|
type SupportedLanguage = (typeof supportedLanguages)[number];
|
|
2012
2429
|
|
|
2013
|
-
const
|
|
2430
|
+
const localisedUrls = ultramodernLocalisedUrls as Record<
|
|
2431
|
+
string,
|
|
2432
|
+
Record<SupportedLanguage, string>
|
|
2433
|
+
>;
|
|
2434
|
+
|
|
2435
|
+
const isSupportedLanguage = (value: string): value is SupportedLanguage =>
|
|
2436
|
+
supportedLanguages.includes(value as SupportedLanguage);
|
|
2437
|
+
|
|
2438
|
+
const normalisePath = (pathname: string) => {
|
|
2439
|
+
const normalised = pathname.replace(/\\/+$/u, '').replace(/\\/+/gu, '/');
|
|
2440
|
+
return normalised.length > 0 ? normalised : '/';
|
|
2441
|
+
};
|
|
2442
|
+
|
|
2443
|
+
const stripLanguagePrefix = (pathname: string) => {
|
|
2444
|
+
const segments = normalisePath(pathname).split('/').filter(Boolean);
|
|
2445
|
+
if (segments.length > 0 && isSupportedLanguage(segments[0] ?? '')) {
|
|
2446
|
+
segments.shift();
|
|
2447
|
+
}
|
|
2448
|
+
return \`/\${segments.join('/')}\`;
|
|
2449
|
+
};
|
|
2450
|
+
|
|
2451
|
+
const escapeRegExp = (value: string) =>
|
|
2452
|
+
value.replace(/[.*+?^\${}()|[\\]\\\\]/g, '\\\\$&');
|
|
2453
|
+
|
|
2454
|
+
const paramName = (segment: string) => segment.slice(1).replace(/\\?$/u, '');
|
|
2455
|
+
|
|
2456
|
+
const matchPattern = (pathname: string, pattern: string) => {
|
|
2457
|
+
const names: string[] = [];
|
|
2458
|
+
const source = normalisePath(pattern)
|
|
2459
|
+
.split('/')
|
|
2460
|
+
.filter(Boolean)
|
|
2461
|
+
.map(segment => {
|
|
2462
|
+
if (segment.startsWith(':')) {
|
|
2463
|
+
names.push(paramName(segment));
|
|
2464
|
+
return segment.endsWith('?') ? '(?:/([^/]+))?' : '/([^/]+)';
|
|
2465
|
+
}
|
|
2466
|
+
return \`/\${escapeRegExp(segment)}\`;
|
|
2467
|
+
})
|
|
2468
|
+
.join('');
|
|
2469
|
+
const match = new RegExp(\`^\${source || '/'}$\`).exec(normalisePath(pathname));
|
|
2470
|
+
|
|
2471
|
+
if (!match) {
|
|
2472
|
+
return undefined;
|
|
2473
|
+
}
|
|
2474
|
+
|
|
2475
|
+
return names.reduce<Record<string, string>>((params, name, index) => {
|
|
2476
|
+
params[name] = decodeURIComponent(match[index + 1] ?? '');
|
|
2477
|
+
return params;
|
|
2478
|
+
}, {});
|
|
2479
|
+
};
|
|
2480
|
+
|
|
2481
|
+
const buildPath = (pattern: string, params: Record<string, string>) => {
|
|
2482
|
+
const path = normalisePath(pattern)
|
|
2483
|
+
.split('/')
|
|
2484
|
+
.filter(Boolean)
|
|
2485
|
+
.map(segment => {
|
|
2486
|
+
if (!segment.startsWith(':')) {
|
|
2487
|
+
return segment;
|
|
2488
|
+
}
|
|
2489
|
+
const value = params[paramName(segment)];
|
|
2490
|
+
return value ? encodeURIComponent(value) : '';
|
|
2491
|
+
})
|
|
2492
|
+
.filter(Boolean)
|
|
2493
|
+
.join('/');
|
|
2494
|
+
|
|
2495
|
+
return \`/\${path}\`;
|
|
2496
|
+
};
|
|
2497
|
+
|
|
2498
|
+
const resolveLocalisedPath = (
|
|
2499
|
+
pathname: string,
|
|
2500
|
+
targetLanguage: SupportedLanguage,
|
|
2501
|
+
) => {
|
|
2502
|
+
const pathWithoutLanguage = stripLanguagePrefix(pathname);
|
|
2503
|
+
|
|
2504
|
+
for (const entry of Object.values(localisedUrls)) {
|
|
2505
|
+
const targetPattern = entry[targetLanguage];
|
|
2506
|
+
if (!targetPattern) {
|
|
2507
|
+
continue;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
for (const language of supportedLanguages) {
|
|
2511
|
+
const sourcePattern = entry[language];
|
|
2512
|
+
const params = sourcePattern
|
|
2513
|
+
? matchPattern(pathWithoutLanguage, sourcePattern)
|
|
2514
|
+
: undefined;
|
|
2515
|
+
if (params) {
|
|
2516
|
+
return buildPath(targetPattern, params);
|
|
2517
|
+
}
|
|
2518
|
+
}
|
|
2519
|
+
}
|
|
2520
|
+
|
|
2521
|
+
return pathWithoutLanguage;
|
|
2522
|
+
};
|
|
2523
|
+
|
|
2524
|
+
const localizedPath = (pathname: string, language: SupportedLanguage) => {
|
|
2525
|
+
const pathWithoutLanguage = resolveLocalisedPath(pathname, language);
|
|
2526
|
+
return pathWithoutLanguage === '/' ? \`/\${language}\` : \`/\${language}\${pathWithoutLanguage}\`;
|
|
2527
|
+
};
|
|
2014
2528
|
|
|
2015
2529
|
const absoluteUrl = (pathname: string) => {
|
|
2016
2530
|
const origin = ULTRAMODERN_SITE_URL.replace(/\\/+$/u, '');
|
|
2017
2531
|
return \`\${origin}\${pathname}\`;
|
|
2018
2532
|
};
|
|
2533
|
+
|
|
2534
|
+
const locationSuffix = (location: {
|
|
2535
|
+
hash?: unknown;
|
|
2536
|
+
search?: unknown;
|
|
2537
|
+
searchStr?: unknown;
|
|
2538
|
+
}) => {
|
|
2539
|
+
const locationSearch =
|
|
2540
|
+
typeof location.searchStr === 'string'
|
|
2541
|
+
? location.searchStr
|
|
2542
|
+
: typeof location.search === 'string'
|
|
2543
|
+
? location.search
|
|
2544
|
+
: '';
|
|
2545
|
+
const locationHash = typeof location.hash === 'string' ? location.hash : '';
|
|
2546
|
+
|
|
2547
|
+
return \`\${locationSearch}\${locationHash}\`;
|
|
2548
|
+
};
|
|
2549
|
+
|
|
2019
2550
|
const LocalizedHead = () => {
|
|
2020
|
-
const
|
|
2551
|
+
const location = useLocation();
|
|
2552
|
+
const canonicalPath = localizedPath(location.pathname, fallbackLanguage);
|
|
2021
2553
|
|
|
2022
2554
|
return (
|
|
2023
|
-
|
|
2555
|
+
<Helmet>
|
|
2024
2556
|
<link rel="canonical" href={absoluteUrl(canonicalPath)} />
|
|
2025
2557
|
{supportedLanguages.map(code => (
|
|
2026
2558
|
<link
|
|
2027
|
-
href={absoluteUrl(localizedPath(code))}
|
|
2559
|
+
href={absoluteUrl(localizedPath(location.pathname, code))}
|
|
2028
2560
|
hrefLang={code}
|
|
2029
2561
|
key={code}
|
|
2030
2562
|
rel="alternate"
|
|
2031
2563
|
/>
|
|
2032
2564
|
))}
|
|
2033
2565
|
<link
|
|
2034
|
-
href={absoluteUrl(localizedPath(fallbackLanguage))}
|
|
2566
|
+
href={absoluteUrl(localizedPath(location.pathname, fallbackLanguage))}
|
|
2035
2567
|
hrefLang="x-default"
|
|
2036
2568
|
rel="alternate"
|
|
2037
2569
|
/>
|
|
2038
|
-
|
|
2570
|
+
</Helmet>
|
|
2039
2571
|
);
|
|
2040
2572
|
};
|
|
2041
2573
|
`;
|
|
2042
2574
|
}
|
|
2043
2575
|
function createShellPage() {
|
|
2044
2576
|
return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2577
|
+
import { Helmet } from '@modern-js/runtime/head';
|
|
2578
|
+
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
2579
|
+
import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
|
|
2045
2580
|
import { ultramodernUiMarker } from '../../ultramodern-build';
|
|
2046
2581
|
|
|
2047
2582
|
const languageCodes = ['en', 'cs'] as const;
|
|
2048
2583
|
|
|
2049
|
-
const remoteKeys = ['
|
|
2584
|
+
const remoteKeys = ['explore', 'decide', 'checkout'] as const;
|
|
2050
2585
|
|
|
2051
2586
|
${createLocalizedHeadComponent()}
|
|
2052
2587
|
export default function ShellHome() {
|
|
2053
2588
|
const { i18nInstance, language } = useModernI18n();
|
|
2054
2589
|
const t = i18nInstance.t.bind(i18nInstance);
|
|
2590
|
+
const location = useLocation();
|
|
2591
|
+
const suffix = locationSuffix(location);
|
|
2055
2592
|
|
|
2056
2593
|
return (
|
|
2057
2594
|
<main>
|
|
@@ -2060,7 +2597,7 @@ export default function ShellHome() {
|
|
|
2060
2597
|
{languageCodes.map(code => (
|
|
2061
2598
|
<a
|
|
2062
2599
|
aria-current={language === code ? 'page' : undefined}
|
|
2063
|
-
href={
|
|
2600
|
+
href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
|
|
2064
2601
|
key={code}
|
|
2065
2602
|
>
|
|
2066
2603
|
{t(\`shell.language.\${code}\`)}
|
|
@@ -2085,9 +2622,12 @@ export default function ShellHome() {
|
|
|
2085
2622
|
function createRemotePage(app) {
|
|
2086
2623
|
if ('remote-commerce' === app.id) return createCommerceRemotePage(app);
|
|
2087
2624
|
const effectBffImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2625
|
+
import { Helmet } from '@modern-js/runtime/head';
|
|
2626
|
+
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
2088
2627
|
import { useEffect, useState } from 'react';
|
|
2628
|
+
import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
|
|
2089
2629
|
import { ultramodernUiMarker } from '../../ultramodern-build';
|
|
2090
|
-
` : "import { useModernI18n } from '@modern-js/plugin-i18n/runtime';\nimport { ultramodernUiMarker } from '../../ultramodern-build';\n";
|
|
2630
|
+
` : "import { useModernI18n } from '@modern-js/plugin-i18n/runtime';\nimport { Helmet } from '@modern-js/runtime/head';\nimport { useLocation } from '@modern-js/plugin-tanstack/runtime';\nimport { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';\nimport { ultramodernUiMarker } from '../../ultramodern-build';\n";
|
|
2091
2631
|
const effectBffState = appHasEffectApi(app) ? ` const [effectApiStatus, setEffectApiStatus] = useState('pending');
|
|
2092
2632
|
|
|
2093
2633
|
useEffect(() => {
|
|
@@ -2119,6 +2659,8 @@ ${createLocalizedHeadComponent()}
|
|
|
2119
2659
|
export default function ${toPascalCase(app.id)}Home() {
|
|
2120
2660
|
const { i18nInstance, language } = useModernI18n();
|
|
2121
2661
|
const t = i18nInstance.t.bind(i18nInstance);
|
|
2662
|
+
const location = useLocation();
|
|
2663
|
+
const suffix = locationSuffix(location);
|
|
2122
2664
|
${effectBffState} return (
|
|
2123
2665
|
<main>
|
|
2124
2666
|
<LocalizedHead />
|
|
@@ -2126,7 +2668,7 @@ ${effectBffState} return (
|
|
|
2126
2668
|
{supportedLanguages.map(code => (
|
|
2127
2669
|
<a
|
|
2128
2670
|
aria-current={language === code ? 'page' : undefined}
|
|
2129
|
-
href={
|
|
2671
|
+
href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
|
|
2130
2672
|
key={code}
|
|
2131
2673
|
>
|
|
2132
2674
|
{t(\`${app.domain}.language.\${code}\`)}
|
|
@@ -2145,7 +2687,10 @@ ${effectBffMarkup} </main>
|
|
|
2145
2687
|
}
|
|
2146
2688
|
function createCommerceRemotePage(app) {
|
|
2147
2689
|
return `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
|
|
2690
|
+
import { Helmet } from '@modern-js/runtime/head';
|
|
2691
|
+
import { useLocation } from '@modern-js/plugin-tanstack/runtime';
|
|
2148
2692
|
import { useEffect, useState, type CSSProperties } from 'react';
|
|
2693
|
+
import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
|
|
2149
2694
|
import { ultramodernUiMarker } from '../../ultramodern-build';
|
|
2150
2695
|
|
|
2151
2696
|
const languageCodes = ['en', 'cs'] as const;
|
|
@@ -2318,6 +2863,8 @@ function BoundaryOverlay({
|
|
|
2318
2863
|
export default function ${toPascalCase(app.id)}Home() {
|
|
2319
2864
|
const { i18nInstance, language } = useModernI18n();
|
|
2320
2865
|
const t = i18nInstance.t.bind(i18nInstance);
|
|
2866
|
+
const location = useLocation();
|
|
2867
|
+
const suffix = locationSuffix(location);
|
|
2321
2868
|
const [cart, setCart] = useState<CartState>({});
|
|
2322
2869
|
const [showBoundaries, setShowBoundaries] = useState(false);
|
|
2323
2870
|
const [effectApiStatus, setEffectApiStatus] = useState('pending');
|
|
@@ -2409,7 +2956,7 @@ export default function ${toPascalCase(app.id)}Home() {
|
|
|
2409
2956
|
<a
|
|
2410
2957
|
aria-current={language === code ? 'page' : undefined}
|
|
2411
2958
|
className="commerce-pill"
|
|
2412
|
-
href={
|
|
2959
|
+
href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
|
|
2413
2960
|
key={code}
|
|
2414
2961
|
>
|
|
2415
2962
|
{t(\`commerce.language.\${code}\`)}
|
|
@@ -2550,10 +3097,21 @@ export default function Layout() {
|
|
|
2550
3097
|
`;
|
|
2551
3098
|
}
|
|
2552
3099
|
function createRemoteEntry(app) {
|
|
2553
|
-
return `export { default } from './components
|
|
3100
|
+
if (app.exposes?.['./ProductPage']) return `export { default } from './components/product-page';
|
|
2554
3101
|
`;
|
|
2555
|
-
}
|
|
2556
|
-
|
|
3102
|
+
if (app.exposes?.['./CartPage']) return `export { default } from './components/cart-page';
|
|
3103
|
+
`;
|
|
3104
|
+
return `export default function ${toPascalCase(app.domain ?? app.id)}Route() {
|
|
3105
|
+
return (
|
|
3106
|
+
<section data-mf-remote="${app.id}" data-mf-expose="./Route">
|
|
3107
|
+
<h2>${app.displayName}</h2>
|
|
3108
|
+
<p>Route surface for ${app.domain ?? app.id}.</p>
|
|
3109
|
+
</section>
|
|
3110
|
+
);
|
|
3111
|
+
}
|
|
3112
|
+
`;
|
|
3113
|
+
}
|
|
3114
|
+
function createRemoteWidget(app) {
|
|
2557
3115
|
const componentName = `${toPascalCase(app.domain ?? app.id)}Widget`;
|
|
2558
3116
|
const body = 'vertical' === app.kind ? `Owns the ${app.domain} vertical route surface.` : 'Provides shared UI primitives for the workspace.';
|
|
2559
3117
|
return `export default function ${componentName}() {
|
|
@@ -2566,16 +3124,57 @@ function createRemoteWidget(app) {
|
|
|
2566
3124
|
}
|
|
2567
3125
|
`;
|
|
2568
3126
|
}
|
|
3127
|
+
function createRemoteExposeComponent(app, expose) {
|
|
3128
|
+
if ('./Widget' === expose) return createRemoteWidget(app);
|
|
3129
|
+
const componentName = `${toPascalCase(app.domain ?? app.id)}${toPascalCase(expose.replace(/^\.\//u, ''))}`;
|
|
3130
|
+
if ('remote-decide' === app.id && './ProductPage' === expose) return `import AddToCart from 'checkout/AddToCart';
|
|
3131
|
+
import Recommendations from 'explore/Recommendations';
|
|
3132
|
+
|
|
3133
|
+
export default function ${componentName}() {
|
|
3134
|
+
return (
|
|
3135
|
+
<section data-mf-remote="${app.id}" data-mf-expose="${expose}">
|
|
3136
|
+
<p>Decide owns tractor product selection.</p>
|
|
3137
|
+
<h2>Field Loader 112</h2>
|
|
3138
|
+
<p>Hydraulic-ready compact tractor with guided implement matching.</p>
|
|
3139
|
+
<AddToCart />
|
|
3140
|
+
<Recommendations />
|
|
3141
|
+
</section>
|
|
3142
|
+
);
|
|
3143
|
+
}
|
|
3144
|
+
`;
|
|
3145
|
+
return `export default function ${componentName}() {
|
|
3146
|
+
return (
|
|
3147
|
+
<section data-mf-remote="${app.id}" data-mf-expose="${expose}">
|
|
3148
|
+
<h2>${app.displayName} ${expose.replace(/^\.\//u, '')}</h2>
|
|
3149
|
+
<p>Module Federation surface owned by ${app.ownership.team}.</p>
|
|
3150
|
+
</section>
|
|
3151
|
+
);
|
|
3152
|
+
}
|
|
3153
|
+
`;
|
|
3154
|
+
}
|
|
3155
|
+
function remoteComponentOutputPath(app, expose) {
|
|
3156
|
+
const exposePath = app.exposes?.[expose];
|
|
3157
|
+
if (!exposePath?.startsWith('./src/components/')) return;
|
|
3158
|
+
return `${app.directory}/${exposePath.replace(/^\.\//u, '')}`;
|
|
3159
|
+
}
|
|
2569
3160
|
function createAppLocaleMessages(app, language) {
|
|
2570
3161
|
const czechLabels = {
|
|
2571
|
-
|
|
2572
|
-
role: '
|
|
2573
|
-
title: '
|
|
3162
|
+
checkout: {
|
|
3163
|
+
role: 'pokladna',
|
|
3164
|
+
title: 'Pokladní remote'
|
|
3165
|
+
},
|
|
3166
|
+
decide: {
|
|
3167
|
+
role: 'rozhodování',
|
|
3168
|
+
title: 'Rozhodovací remote'
|
|
2574
3169
|
},
|
|
2575
3170
|
'design-system': {
|
|
2576
3171
|
role: 'design system',
|
|
2577
3172
|
title: 'Design system remote'
|
|
2578
3173
|
},
|
|
3174
|
+
explore: {
|
|
3175
|
+
role: 'procházení',
|
|
3176
|
+
title: 'Průzkumný remote'
|
|
3177
|
+
},
|
|
2579
3178
|
identity: {
|
|
2580
3179
|
role: 'identita',
|
|
2581
3180
|
title: 'Identitní remote'
|
|
@@ -2589,9 +3188,12 @@ function createAppLocaleMessages(app, language) {
|
|
|
2589
3188
|
switcher: 'en' === language ? 'Language' : 'Jazyk'
|
|
2590
3189
|
},
|
|
2591
3190
|
remotes: {
|
|
2592
|
-
|
|
2593
|
-
|
|
2594
|
-
|
|
3191
|
+
checkout: 'en' === language ? 'Checkout Remote' : 'Checkout remote',
|
|
3192
|
+
decide: 'en' === language ? 'Decide Remote' : 'Decide remote',
|
|
3193
|
+
explore: 'en' === language ? 'Explore Remote' : 'Explore remote'
|
|
3194
|
+
},
|
|
3195
|
+
routes: {
|
|
3196
|
+
home: 'en' === language ? 'Home' : 'Domů'
|
|
2595
3197
|
},
|
|
2596
3198
|
title: 'en' === language ? 'UltraModern SuperApp Shell' : 'UltraModern SuperApp shell'
|
|
2597
3199
|
}
|
|
@@ -2672,6 +3274,16 @@ function createAppLocaleMessages(app, language) {
|
|
|
2672
3274
|
switcher: 'en' === language ? 'Language' : 'Jazyk'
|
|
2673
3275
|
},
|
|
2674
3276
|
role: 'en' === language ? app.domain ?? app.kind : czechLabel.role,
|
|
3277
|
+
routes: {
|
|
3278
|
+
cart: 'en' === language ? 'Cart' : 'Košík',
|
|
3279
|
+
checkout: 'en' === language ? 'Checkout' : 'Pokladna',
|
|
3280
|
+
home: 'en' === language ? 'Home' : 'Domů',
|
|
3281
|
+
listing: 'en' === language ? 'Tractors' : 'Traktory',
|
|
3282
|
+
productDetail: 'en' === language ? 'Tractor detail' : 'Detail traktoru',
|
|
3283
|
+
storePicker: 'en' === language ? 'Store picker' : 'Výběr prodejce',
|
|
3284
|
+
thankYou: 'en' === language ? 'Order confirmation' : 'Potvrzení objednávky',
|
|
3285
|
+
unavailable: 'en' === language ? 'Unavailable' : 'Nedostupné'
|
|
3286
|
+
},
|
|
2675
3287
|
title: 'en' === language ? app.displayName : czechLabel.title
|
|
2676
3288
|
}
|
|
2677
3289
|
};
|
|
@@ -2706,6 +3318,18 @@ function createDesignTokens() {
|
|
|
2706
3318
|
} as const;
|
|
2707
3319
|
`;
|
|
2708
3320
|
}
|
|
3321
|
+
function createSharedDesignTokensCss() {
|
|
3322
|
+
return `@layer ultramodern-shared-tokens {
|
|
3323
|
+
:root {
|
|
3324
|
+
--um-color-accent: #2f8f68;
|
|
3325
|
+
--um-color-canvas: #f1eadc;
|
|
3326
|
+
--um-color-foreground: #133225;
|
|
3327
|
+
--um-color-link: #166b4b;
|
|
3328
|
+
--um-color-surface: #f6fbf7;
|
|
3329
|
+
}
|
|
3330
|
+
}
|
|
3331
|
+
`;
|
|
3332
|
+
}
|
|
2709
3333
|
function serviceEffectApiExport(service = effectService) {
|
|
2710
3334
|
return `${toCamelCase(effectApiStem(service))}EffectApi`;
|
|
2711
3335
|
}
|
|
@@ -2718,6 +3342,12 @@ function serviceEffectApiName(service = effectService) {
|
|
|
2718
3342
|
function serviceEffectSchemaExport(service = effectService) {
|
|
2719
3343
|
return `${toCamelCase(effectApiStem(service))}ItemSchema`;
|
|
2720
3344
|
}
|
|
3345
|
+
function serviceEffectMarkerSchemaExport(service = effectService) {
|
|
3346
|
+
return `${toCamelCase(effectApiStem(service))}MarkerSchema`;
|
|
3347
|
+
}
|
|
3348
|
+
function serviceEffectReadinessSchemaExport(service = effectService) {
|
|
3349
|
+
return `${toCamelCase(effectApiStem(service))}ReadinessSchema`;
|
|
3350
|
+
}
|
|
2721
3351
|
function serviceEffectErrorStem(service = effectService) {
|
|
2722
3352
|
const stem = effectApiStem(service);
|
|
2723
3353
|
return 'recommendations' === stem ? 'recommendation' : stem;
|
|
@@ -2743,6 +3373,8 @@ function createEffectSharedApiImports() {
|
|
|
2743
3373
|
}
|
|
2744
3374
|
function createEffectSharedApiContract(service = effectService) {
|
|
2745
3375
|
const schemaExport = serviceEffectSchemaExport(service);
|
|
3376
|
+
const markerSchemaExport = serviceEffectMarkerSchemaExport(service);
|
|
3377
|
+
const readinessSchemaExport = serviceEffectReadinessSchemaExport(service);
|
|
2746
3378
|
const createPayloadSchemaExport = serviceEffectCreatePayloadSchemaExport(service);
|
|
2747
3379
|
const notFoundErrorExport = serviceEffectNotFoundErrorExport(service);
|
|
2748
3380
|
const notFoundSchemaExport = serviceEffectNotFoundSchemaExport(service);
|
|
@@ -2751,19 +3383,33 @@ function createEffectSharedApiContract(service = effectService) {
|
|
|
2751
3383
|
const groupName = serviceEffectGroupName(service);
|
|
2752
3384
|
const stem = effectApiStem(service);
|
|
2753
3385
|
const servicePrefix = effectApiPrefix(service);
|
|
2754
|
-
return `export const ${
|
|
3386
|
+
return `export const ${markerSchemaExport} = Schema.Struct({
|
|
3387
|
+
appId: Schema.String,
|
|
3388
|
+
packageName: Schema.String,
|
|
3389
|
+
version: Schema.String,
|
|
3390
|
+
build: Schema.String,
|
|
3391
|
+
deployProfile: Schema.String,
|
|
3392
|
+
surface: Schema.String,
|
|
3393
|
+
});
|
|
3394
|
+
|
|
3395
|
+
export const ${schemaExport} = Schema.Struct({
|
|
2755
3396
|
id: Schema.String,
|
|
2756
|
-
marker:
|
|
2757
|
-
appId: Schema.String,
|
|
2758
|
-
packageName: Schema.String,
|
|
2759
|
-
version: Schema.String,
|
|
2760
|
-
build: Schema.String,
|
|
2761
|
-
deployProfile: Schema.String,
|
|
2762
|
-
surface: Schema.String,
|
|
2763
|
-
}),
|
|
3397
|
+
marker: ${markerSchemaExport},
|
|
2764
3398
|
title: Schema.String,
|
|
2765
3399
|
});
|
|
2766
3400
|
|
|
3401
|
+
export const ${readinessSchemaExport} = Schema.Struct({
|
|
3402
|
+
checks: Schema.Struct({
|
|
3403
|
+
effectBff: Schema.Literal('ready'),
|
|
3404
|
+
moduleFederation: Schema.Literal('ready'),
|
|
3405
|
+
ssr: Schema.Literal('ready'),
|
|
3406
|
+
translations: Schema.Literal('ready'),
|
|
3407
|
+
}),
|
|
3408
|
+
marker: ${markerSchemaExport},
|
|
3409
|
+
status: Schema.Literal('ready'),
|
|
3410
|
+
versionSkew: Schema.Literal('none'),
|
|
3411
|
+
});
|
|
3412
|
+
|
|
2767
3413
|
export const ${createPayloadSchemaExport} = Schema.Struct({
|
|
2768
3414
|
title: Schema.String,
|
|
2769
3415
|
});
|
|
@@ -2799,6 +3445,11 @@ export const ${apiExport} = HttpApi.make('${apiName}').add(
|
|
|
2799
3445
|
}),
|
|
2800
3446
|
}),
|
|
2801
3447
|
)
|
|
3448
|
+
.add(
|
|
3449
|
+
HttpApiEndpoint.get('readiness', '/effect/${stem}/readiness', {
|
|
3450
|
+
success: ${readinessSchemaExport},
|
|
3451
|
+
}),
|
|
3452
|
+
)
|
|
2802
3453
|
.add(
|
|
2803
3454
|
HttpApiEndpoint.get('get', '/effect/${stem}/:id', {
|
|
2804
3455
|
params: {
|
|
@@ -2825,6 +3476,12 @@ export const ${groupName}OperationContexts = {
|
|
|
2825
3476
|
method: 'GET',
|
|
2826
3477
|
source: 'generated-client',
|
|
2827
3478
|
},
|
|
3479
|
+
readiness: {
|
|
3480
|
+
operationId: '${apiName}:${groupName}:readiness',
|
|
3481
|
+
routePath: '/effect/${stem}/readiness',
|
|
3482
|
+
method: 'GET',
|
|
3483
|
+
source: 'generated-client',
|
|
3484
|
+
},
|
|
2828
3485
|
get: {
|
|
2829
3486
|
operationId: '${apiName}:${groupName}:get',
|
|
2830
3487
|
routePath: '/effect/${stem}/:id',
|
|
@@ -2843,6 +3500,7 @@ export const ${groupName}ApiContract = {
|
|
|
2843
3500
|
basePath: '${servicePrefix}/effect/${stem}',
|
|
2844
3501
|
ownerId: '${service.id}',
|
|
2845
3502
|
servicePrefix: '${servicePrefix}',
|
|
3503
|
+
readinessPath: '${servicePrefix}/effect/${stem}/readiness',
|
|
2846
3504
|
} as const;
|
|
2847
3505
|
`;
|
|
2848
3506
|
}
|
|
@@ -2911,6 +3569,24 @@ const ${groupName}Layer = HttpApiBuilder.group(
|
|
|
2911
3569
|
}),
|
|
2912
3570
|
),
|
|
2913
3571
|
)
|
|
3572
|
+
.handle('readiness', () =>
|
|
3573
|
+
Effect.succeed({
|
|
3574
|
+
checks: {
|
|
3575
|
+
effectBff: 'ready' as const,
|
|
3576
|
+
moduleFederation: 'ready' as const,
|
|
3577
|
+
ssr: 'ready' as const,
|
|
3578
|
+
translations: 'ready' as const,
|
|
3579
|
+
},
|
|
3580
|
+
marker: ultramodernApiMarker,
|
|
3581
|
+
status: 'ready' as const,
|
|
3582
|
+
versionSkew: 'none' as const,
|
|
3583
|
+
}).pipe(
|
|
3584
|
+
Effect.withSpan('ultramodern.effect.${groupName}.readiness', {
|
|
3585
|
+
attributes: operationAttributes(${groupName}OperationContexts.readiness),
|
|
3586
|
+
kind: 'server',
|
|
3587
|
+
}),
|
|
3588
|
+
),
|
|
3589
|
+
)
|
|
2914
3590
|
.handle('get', ({ params }) => {
|
|
2915
3591
|
const item = ${groupName}Items.find(item => item.id === params.id);
|
|
2916
3592
|
return (item !== undefined
|
|
@@ -2960,6 +3636,7 @@ function createEffectClient(service, contractImportPath) {
|
|
|
2960
3636
|
const clientOptionsName = `${toPascalCase(stem)}ClientOptions`;
|
|
2961
3637
|
const createClientName = `create${toPascalCase(stem)}Client`;
|
|
2962
3638
|
const listName = `list${toPascalCase(stem)}`;
|
|
3639
|
+
const readinessName = `get${toPascalCase(stem)}Readiness`;
|
|
2963
3640
|
const getName = `get${toPascalCase(singular)}`;
|
|
2964
3641
|
const createName = `create${toPascalCase(singular)}`;
|
|
2965
3642
|
return `import {
|
|
@@ -3004,6 +3681,20 @@ export function ${listName}(
|
|
|
3004
3681
|
);
|
|
3005
3682
|
}
|
|
3006
3683
|
|
|
3684
|
+
export function ${readinessName}(
|
|
3685
|
+
options: ${clientOptionsName} = {},
|
|
3686
|
+
) {
|
|
3687
|
+
return runEffectRequest(
|
|
3688
|
+
${createClientName}({
|
|
3689
|
+
...options,
|
|
3690
|
+
operationContext:
|
|
3691
|
+
options.operationContext ?? ${groupName}OperationContexts.readiness,
|
|
3692
|
+
}),
|
|
3693
|
+
).then(client =>
|
|
3694
|
+
runEffectRequest(client.${groupName}.readiness({})),
|
|
3695
|
+
);
|
|
3696
|
+
}
|
|
3697
|
+
|
|
3007
3698
|
export function ${getName}(
|
|
3008
3699
|
id: string,
|
|
3009
3700
|
options: ${clientOptionsName} = {},
|
|
@@ -3039,17 +3730,141 @@ export function ${createName}(
|
|
|
3039
3730
|
}
|
|
3040
3731
|
function createShellEffectClient(scope) {
|
|
3041
3732
|
return `export {
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
3047
|
-
|
|
3733
|
+
createCheckout,
|
|
3734
|
+
createCheckoutClient,
|
|
3735
|
+
getCheckout,
|
|
3736
|
+
getCheckoutReadiness,
|
|
3737
|
+
listCheckout,
|
|
3738
|
+
type CheckoutClientOptions,
|
|
3739
|
+
} from '${ultramodern_workspace_packageName(scope, 'remote-checkout')}/effect/client';
|
|
3740
|
+
|
|
3741
|
+
export {
|
|
3742
|
+
createDecide,
|
|
3743
|
+
createDecideClient,
|
|
3744
|
+
getDecide,
|
|
3745
|
+
getDecideReadiness,
|
|
3746
|
+
listDecide,
|
|
3747
|
+
type DecideClientOptions,
|
|
3748
|
+
} from '${ultramodern_workspace_packageName(scope, 'remote-decide')}/effect/client';
|
|
3749
|
+
|
|
3750
|
+
export {
|
|
3751
|
+
createExplore,
|
|
3752
|
+
createExploreClient,
|
|
3753
|
+
getExplore,
|
|
3754
|
+
getExploreReadiness,
|
|
3755
|
+
listExplore,
|
|
3756
|
+
type ExploreClientOptions,
|
|
3757
|
+
} from '${ultramodern_workspace_packageName(scope, 'remote-explore')}/effect/client';
|
|
3048
3758
|
`;
|
|
3049
3759
|
}
|
|
3050
3760
|
function toPascalCase(value) {
|
|
3051
3761
|
return value.split(/[-_]+/).filter(Boolean).map((part)=>`${part.charAt(0).toUpperCase()}${part.slice(1)}`).join('');
|
|
3052
3762
|
}
|
|
3763
|
+
function createEffectReadinessContract(app) {
|
|
3764
|
+
const stem = effectApiStem(app);
|
|
3765
|
+
return {
|
|
3766
|
+
endpoint: `/effect/${stem}/readiness`,
|
|
3767
|
+
marker: {
|
|
3768
|
+
ui: 'ultramodernUiMarker',
|
|
3769
|
+
api: 'ultramodernApiMarker',
|
|
3770
|
+
skew: 'none'
|
|
3771
|
+
},
|
|
3772
|
+
checks: [
|
|
3773
|
+
'moduleFederation',
|
|
3774
|
+
'ssr',
|
|
3775
|
+
'translations',
|
|
3776
|
+
'effectBff'
|
|
3777
|
+
]
|
|
3778
|
+
};
|
|
3779
|
+
}
|
|
3780
|
+
function createEffectRequestContextContract() {
|
|
3781
|
+
return {
|
|
3782
|
+
propagatedHeaders: [
|
|
3783
|
+
'accept-language',
|
|
3784
|
+
'authorization',
|
|
3785
|
+
'traceparent',
|
|
3786
|
+
'x-correlation-id',
|
|
3787
|
+
'x-tenant-id',
|
|
3788
|
+
'x-ultramodern-env',
|
|
3789
|
+
'x-vertical-version-id'
|
|
3790
|
+
],
|
|
3791
|
+
source: 'shell-to-vertical-effect-client'
|
|
3792
|
+
};
|
|
3793
|
+
}
|
|
3794
|
+
function createEffectDomainOperations(app) {
|
|
3795
|
+
const stem = effectApiStem(app);
|
|
3796
|
+
const group = serviceEffectGroupName(app);
|
|
3797
|
+
const basePath = `/effect/${stem}`;
|
|
3798
|
+
if ('checkout' === stem) return {
|
|
3799
|
+
cartSnapshot: {
|
|
3800
|
+
client: 'listCheckout',
|
|
3801
|
+
method: 'GET',
|
|
3802
|
+
path: basePath,
|
|
3803
|
+
resource: 'cart',
|
|
3804
|
+
owner: app.id
|
|
3805
|
+
},
|
|
3806
|
+
cartMutation: {
|
|
3807
|
+
client: 'createCheckout',
|
|
3808
|
+
method: 'POST',
|
|
3809
|
+
path: basePath,
|
|
3810
|
+
resource: 'cart-line',
|
|
3811
|
+
owner: app.id
|
|
3812
|
+
},
|
|
3813
|
+
orderConfirmation: {
|
|
3814
|
+
client: 'getCheckout',
|
|
3815
|
+
method: 'GET',
|
|
3816
|
+
path: `${basePath}/:id`,
|
|
3817
|
+
resource: 'order',
|
|
3818
|
+
owner: app.id
|
|
3819
|
+
}
|
|
3820
|
+
};
|
|
3821
|
+
if ('decide' === stem) return {
|
|
3822
|
+
productDetail: {
|
|
3823
|
+
client: 'getDecide',
|
|
3824
|
+
method: 'GET',
|
|
3825
|
+
path: `${basePath}/:id`,
|
|
3826
|
+
resource: 'product-detail',
|
|
3827
|
+
owner: app.id
|
|
3828
|
+
},
|
|
3829
|
+
configurationDraft: {
|
|
3830
|
+
client: 'createDecide',
|
|
3831
|
+
method: 'POST',
|
|
3832
|
+
path: basePath,
|
|
3833
|
+
resource: 'configuration',
|
|
3834
|
+
owner: app.id
|
|
3835
|
+
},
|
|
3836
|
+
productList: {
|
|
3837
|
+
client: 'listDecide',
|
|
3838
|
+
method: 'GET',
|
|
3839
|
+
path: basePath,
|
|
3840
|
+
resource: 'products',
|
|
3841
|
+
owner: app.id
|
|
3842
|
+
}
|
|
3843
|
+
};
|
|
3844
|
+
return {
|
|
3845
|
+
recommendationFeed: {
|
|
3846
|
+
client: `list${toPascalCase(stem)}`,
|
|
3847
|
+
method: 'GET',
|
|
3848
|
+
path: basePath,
|
|
3849
|
+
resource: 'recommendations',
|
|
3850
|
+
owner: app.id
|
|
3851
|
+
},
|
|
3852
|
+
recommendationDetail: {
|
|
3853
|
+
client: `get${toPascalCase(serviceEffectErrorStem(app))}`,
|
|
3854
|
+
method: 'GET',
|
|
3855
|
+
path: `${basePath}/:id`,
|
|
3856
|
+
resource: 'recommendation',
|
|
3857
|
+
owner: app.id
|
|
3858
|
+
},
|
|
3859
|
+
recommendationCreate: {
|
|
3860
|
+
client: `create${toPascalCase(serviceEffectErrorStem(app))}`,
|
|
3861
|
+
method: 'POST',
|
|
3862
|
+
path: basePath,
|
|
3863
|
+
resource: group,
|
|
3864
|
+
owner: app.id
|
|
3865
|
+
}
|
|
3866
|
+
};
|
|
3867
|
+
}
|
|
3053
3868
|
function effectApiTopologyMetadata(app) {
|
|
3054
3869
|
if (!appHasEffectApi(app)) return;
|
|
3055
3870
|
return {
|
|
@@ -3069,7 +3884,10 @@ function effectApiTopologyMetadata(app) {
|
|
|
3069
3884
|
},
|
|
3070
3885
|
serverEntry: `${app.directory}/api/effect/index.ts`,
|
|
3071
3886
|
basePath: `${app.effectApi.prefix}/effect/${app.effectApi.stem}`,
|
|
3072
|
-
consumedBy: app.effectApi.consumedBy
|
|
3887
|
+
consumedBy: app.effectApi.consumedBy,
|
|
3888
|
+
readiness: createEffectReadinessContract(app),
|
|
3889
|
+
requestContext: createEffectRequestContextContract(),
|
|
3890
|
+
domainOperations: createEffectDomainOperations(app)
|
|
3073
3891
|
}
|
|
3074
3892
|
};
|
|
3075
3893
|
}
|
|
@@ -3088,14 +3906,11 @@ function createTopology(scope) {
|
|
|
3088
3906
|
moduleFederation: {
|
|
3089
3907
|
role: 'host',
|
|
3090
3908
|
name: shellApp.mfName,
|
|
3091
|
-
remotes:
|
|
3092
|
-
id: remote.id,
|
|
3093
|
-
name: remote.mfName,
|
|
3094
|
-
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
|
|
3095
|
-
})),
|
|
3909
|
+
remotes: createModuleFederationRemoteContracts(shellApp),
|
|
3096
3910
|
ssr: true,
|
|
3097
3911
|
sharedContractVersion: 'mf-ssr-contract-v1'
|
|
3098
3912
|
},
|
|
3913
|
+
cloudflare: createCloudflareDeployContract(scope, shellApp),
|
|
3099
3914
|
ownership: shellApp.ownership
|
|
3100
3915
|
},
|
|
3101
3916
|
remotes: remoteApps.map((remote)=>({
|
|
@@ -3108,6 +3923,10 @@ function createTopology(scope) {
|
|
|
3108
3923
|
name: remote.mfName,
|
|
3109
3924
|
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`,
|
|
3110
3925
|
exposes: Object.keys(remote.exposes ?? {}),
|
|
3926
|
+
...remote.remoteRefs?.length ? {
|
|
3927
|
+
remoteRefs: remote.remoteRefs,
|
|
3928
|
+
remotes: createModuleFederationRemoteContracts(remote)
|
|
3929
|
+
} : {},
|
|
3111
3930
|
ssr: true,
|
|
3112
3931
|
fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
|
|
3113
3932
|
sharedContractVersion: 'mf-ssr-contract-v1'
|
|
@@ -3115,6 +3934,7 @@ function createTopology(scope) {
|
|
|
3115
3934
|
...effectApiTopologyMetadata(remote) ? {
|
|
3116
3935
|
api: effectApiTopologyMetadata(remote)
|
|
3117
3936
|
} : {},
|
|
3937
|
+
cloudflare: createCloudflareDeployContract(scope, remote),
|
|
3118
3938
|
ownership: remote.ownership
|
|
3119
3939
|
})),
|
|
3120
3940
|
effectServices: [],
|
|
@@ -3225,6 +4045,11 @@ function createEffectOperationContract(target) {
|
|
|
3225
4045
|
path: `/effect/${stem}`,
|
|
3226
4046
|
source: 'generated-client'
|
|
3227
4047
|
},
|
|
4048
|
+
readiness: {
|
|
4049
|
+
method: 'GET',
|
|
4050
|
+
path: `/effect/${stem}/readiness`,
|
|
4051
|
+
source: 'generated-client'
|
|
4052
|
+
},
|
|
3228
4053
|
get: {
|
|
3229
4054
|
method: 'GET',
|
|
3230
4055
|
path: `/effect/${stem}/:id`,
|
|
@@ -3281,7 +4106,143 @@ function createAppConfigContract(app) {
|
|
|
3281
4106
|
} : {}
|
|
3282
4107
|
};
|
|
3283
4108
|
}
|
|
3284
|
-
function
|
|
4109
|
+
function cssLayerName(app) {
|
|
4110
|
+
if ('shell' === app.kind) return 'ultramodern-shell-base';
|
|
4111
|
+
return `ultramodern-remote-${app.domain ?? app.id}`;
|
|
4112
|
+
}
|
|
4113
|
+
function cssRole(app) {
|
|
4114
|
+
if ('shell' === app.kind) return 'shell-base-overlay';
|
|
4115
|
+
return 'horizontal-remote' === app.kind ? 'horizontal-remote-css' : 'vertical-remote-css';
|
|
4116
|
+
}
|
|
4117
|
+
function cssClassPrefix(app) {
|
|
4118
|
+
if ('shell' === app.kind) return 'shell-';
|
|
4119
|
+
return `${app.domain ?? app.id.replace(/^remote-/, '')}-`;
|
|
4120
|
+
}
|
|
4121
|
+
function createCssDedupeContract(scope) {
|
|
4122
|
+
return {
|
|
4123
|
+
strategy: 'shared-token-package-plus-css-content-hash',
|
|
4124
|
+
sharedPackage: ultramodern_workspace_packageName(scope, 'shared-design-tokens'),
|
|
4125
|
+
sharedLayers: [
|
|
4126
|
+
'ultramodern-shared-tokens'
|
|
4127
|
+
],
|
|
4128
|
+
runtimeLoad: 'once-per-content-hash',
|
|
4129
|
+
duplicateBaseStylesAllowed: false
|
|
4130
|
+
};
|
|
4131
|
+
}
|
|
4132
|
+
function createCssSsrContract(app) {
|
|
4133
|
+
return {
|
|
4134
|
+
cloudflare: true,
|
|
4135
|
+
firstPaintRequired: true,
|
|
4136
|
+
linkEmission: 'modern-ssr-css-assets',
|
|
4137
|
+
remoteCss: 'shell' === app.kind ? 'host-preloads-shell-and-shared-css' : 'remote-manifest-owned-css'
|
|
4138
|
+
};
|
|
4139
|
+
}
|
|
4140
|
+
function createAppCssFederationContract(scope, app) {
|
|
4141
|
+
const ownedLayers = 'shell' === app.kind ? [
|
|
4142
|
+
'ultramodern-shell-base',
|
|
4143
|
+
'ultramodern-shell-overlay'
|
|
4144
|
+
] : [
|
|
4145
|
+
cssLayerName(app)
|
|
4146
|
+
];
|
|
4147
|
+
return {
|
|
4148
|
+
owner: {
|
|
4149
|
+
id: app.id,
|
|
4150
|
+
package: ultramodern_workspace_packageName(scope, app.packageSuffix),
|
|
4151
|
+
team: app.ownership.team
|
|
4152
|
+
},
|
|
4153
|
+
role: cssRole(app),
|
|
4154
|
+
rootSelector: `[data-app-id="${app.id}"]`,
|
|
4155
|
+
classPrefix: cssClassPrefix(app),
|
|
4156
|
+
layers: {
|
|
4157
|
+
shared: [
|
|
4158
|
+
'ultramodern-shared-tokens'
|
|
4159
|
+
],
|
|
4160
|
+
owned: ownedLayers,
|
|
4161
|
+
imports: 'shell' === app.kind ? [
|
|
4162
|
+
'ultramodern-shared-tokens'
|
|
4163
|
+
] : [
|
|
4164
|
+
'ultramodern-shared-tokens'
|
|
4165
|
+
]
|
|
4166
|
+
},
|
|
4167
|
+
entrypoints: {
|
|
4168
|
+
layoutImport: 'src/routes/layout.tsx',
|
|
4169
|
+
css: [
|
|
4170
|
+
'src/routes/index.css'
|
|
4171
|
+
],
|
|
4172
|
+
...'shell' !== app.kind ? {
|
|
4173
|
+
remoteEntry: 'src/remote-entry.tsx'
|
|
4174
|
+
} : {}
|
|
4175
|
+
},
|
|
4176
|
+
assets: {
|
|
4177
|
+
shared: [
|
|
4178
|
+
`${ultramodern_workspace_packageName(scope, 'shared-design-tokens')}/tokens.css`
|
|
4179
|
+
],
|
|
4180
|
+
owned: [
|
|
4181
|
+
'src/routes/index.css'
|
|
4182
|
+
],
|
|
4183
|
+
emittedBy: 'modern-rspack-css-extraction',
|
|
4184
|
+
contentHash: true
|
|
4185
|
+
},
|
|
4186
|
+
dedupe: createCssDedupeContract(scope),
|
|
4187
|
+
ssr: createCssSsrContract(app)
|
|
4188
|
+
};
|
|
4189
|
+
}
|
|
4190
|
+
function createCssFederationContract(scope) {
|
|
4191
|
+
return {
|
|
4192
|
+
schemaVersion: 1,
|
|
4193
|
+
sharedDesignTokens: {
|
|
4194
|
+
owner: {
|
|
4195
|
+
id: 'shared-design-tokens',
|
|
4196
|
+
package: ultramodern_workspace_packageName(scope, 'shared-design-tokens'),
|
|
4197
|
+
team: 'super-app-platform'
|
|
4198
|
+
},
|
|
4199
|
+
role: 'shared-design-tokens',
|
|
4200
|
+
rootSelector: ':root',
|
|
4201
|
+
classPrefix: '--um-',
|
|
4202
|
+
layers: {
|
|
4203
|
+
owned: [
|
|
4204
|
+
'ultramodern-shared-tokens'
|
|
4205
|
+
]
|
|
4206
|
+
},
|
|
4207
|
+
entrypoints: {
|
|
4208
|
+
css: [
|
|
4209
|
+
'packages/shared-design-tokens/src/tokens.css'
|
|
4210
|
+
],
|
|
4211
|
+
typescript: [
|
|
4212
|
+
'packages/shared-design-tokens/src/index.ts'
|
|
4213
|
+
]
|
|
4214
|
+
},
|
|
4215
|
+
assets: {
|
|
4216
|
+
exports: [
|
|
4217
|
+
'./tokens.css'
|
|
4218
|
+
],
|
|
4219
|
+
css: [
|
|
4220
|
+
'packages/shared-design-tokens/src/tokens.css'
|
|
4221
|
+
]
|
|
4222
|
+
},
|
|
4223
|
+
dedupe: createCssDedupeContract(scope),
|
|
4224
|
+
ssr: {
|
|
4225
|
+
cloudflare: true,
|
|
4226
|
+
firstPaintRequired: true,
|
|
4227
|
+
importedByApps: true
|
|
4228
|
+
}
|
|
4229
|
+
},
|
|
4230
|
+
ownershipRules: {
|
|
4231
|
+
shell: [
|
|
4232
|
+
'base',
|
|
4233
|
+
'overlay'
|
|
4234
|
+
],
|
|
4235
|
+
remotes: [
|
|
4236
|
+
'vertical-css'
|
|
4237
|
+
],
|
|
4238
|
+
forbiddenRemoteLayers: [
|
|
4239
|
+
'ultramodern-shell-base',
|
|
4240
|
+
'ultramodern-shell-overlay'
|
|
4241
|
+
]
|
|
4242
|
+
}
|
|
4243
|
+
};
|
|
4244
|
+
}
|
|
4245
|
+
function createStylingContract(scope, app, enableTailwind) {
|
|
3285
4246
|
return {
|
|
3286
4247
|
tailwind: enableTailwind,
|
|
3287
4248
|
...enableTailwind ? {
|
|
@@ -3291,21 +4252,28 @@ function createStylingContract(enableTailwind) {
|
|
|
3291
4252
|
contentGlobs: [
|
|
3292
4253
|
'./src/**/*.{js,jsx,ts,tsx}'
|
|
3293
4254
|
]
|
|
3294
|
-
} : {}
|
|
4255
|
+
} : {},
|
|
4256
|
+
federation: createAppCssFederationContract(scope, app)
|
|
3295
4257
|
};
|
|
3296
4258
|
}
|
|
3297
4259
|
function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
3298
|
-
const
|
|
4260
|
+
const appWithResolvedRefs = 'shell' === app.kind ? {
|
|
4261
|
+
...app,
|
|
4262
|
+
remoteRefs: apps.filter((candidate)=>'shell' !== candidate.kind).map((candidate)=>candidate.id)
|
|
4263
|
+
} : app;
|
|
4264
|
+
const consumedRemotes = createModuleFederationRemoteContracts(appWithResolvedRefs, apps);
|
|
3299
4265
|
return {
|
|
3300
4266
|
id: app.id,
|
|
3301
4267
|
package: ultramodern_workspace_packageName(scope, app.packageSuffix),
|
|
3302
4268
|
path: app.directory,
|
|
3303
4269
|
kind: app.kind,
|
|
3304
4270
|
config: createAppConfigContract(app),
|
|
3305
|
-
styling: createStylingContract(enableTailwind),
|
|
4271
|
+
styling: createStylingContract(scope, app, enableTailwind),
|
|
3306
4272
|
deploy: {
|
|
3307
4273
|
target: 'cloudflare',
|
|
4274
|
+
cloudflare: createCloudflareDeployContract(scope, app),
|
|
3308
4275
|
worker: {
|
|
4276
|
+
name: createCloudflareWorkerName(scope, app),
|
|
3309
4277
|
ssr: true
|
|
3310
4278
|
},
|
|
3311
4279
|
output: {
|
|
@@ -3319,25 +4287,42 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
|
3319
4287
|
},
|
|
3320
4288
|
i18n: {
|
|
3321
4289
|
plugin: '@modern-js/plugin-i18n',
|
|
3322
|
-
backend:
|
|
4290
|
+
backend: {
|
|
4291
|
+
enabled: true,
|
|
4292
|
+
loadPath: '/locales/{{lng}}/{{ns}}.json'
|
|
4293
|
+
},
|
|
3323
4294
|
reactI18next: false,
|
|
3324
4295
|
languages: [
|
|
3325
4296
|
'en',
|
|
3326
4297
|
'cs'
|
|
3327
4298
|
],
|
|
3328
4299
|
fallbackLanguage: 'en',
|
|
3329
|
-
|
|
4300
|
+
namespace: appI18nNamespace(app),
|
|
4301
|
+
namespaces: [
|
|
4302
|
+
appI18nNamespace(app),
|
|
4303
|
+
'translation'
|
|
4304
|
+
],
|
|
4305
|
+
publicDir: './locales',
|
|
4306
|
+
localisedUrls: createLocalisedUrlsMap(app),
|
|
4307
|
+
resourceOwnership: {
|
|
4308
|
+
ownerAppId: app.id,
|
|
4309
|
+
source: 'route-owned',
|
|
4310
|
+
staticJson: `./locales/{lng}/${appI18nNamespace(app)}.json`
|
|
4311
|
+
}
|
|
4312
|
+
},
|
|
4313
|
+
routes: {
|
|
4314
|
+
source: 'route-owned',
|
|
4315
|
+
metadataExport: './src/routes/ultramodern-route-metadata',
|
|
4316
|
+
localisedUrls: createLocalisedUrlsMap(app),
|
|
4317
|
+
owned: createRouteOwnedI18nPaths(app),
|
|
4318
|
+
generatedRouteMap: true,
|
|
4319
|
+
manualOverrides: []
|
|
3330
4320
|
},
|
|
3331
4321
|
moduleFederation: {
|
|
3332
4322
|
name: app.mfName,
|
|
3333
|
-
...
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
alias: remoteDependencyAlias(remote),
|
|
3337
|
-
name: remote.mfName,
|
|
3338
|
-
manifestEnv: createRemoteManifestEnv(remote),
|
|
3339
|
-
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`
|
|
3340
|
-
}))
|
|
4323
|
+
...appWithResolvedRefs.remoteRefs?.length ? {
|
|
4324
|
+
remoteRefs: appWithResolvedRefs.remoteRefs,
|
|
4325
|
+
remotes: consumedRemotes
|
|
3341
4326
|
} : {},
|
|
3342
4327
|
exposes: Object.keys(app.exposes ?? {}),
|
|
3343
4328
|
dts: {
|
|
@@ -3409,6 +4394,9 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
|
3409
4394
|
workerEntry: 'worker/__modern_bff_effect.js',
|
|
3410
4395
|
contract: './shared/effect/api',
|
|
3411
4396
|
client: './effect/client',
|
|
4397
|
+
readiness: createEffectReadinessContract(app),
|
|
4398
|
+
requestContext: createEffectRequestContextContract(),
|
|
4399
|
+
domainOperations: createEffectDomainOperations(app),
|
|
3412
4400
|
...createEffectOperationContract(app)
|
|
3413
4401
|
}
|
|
3414
4402
|
} : {}
|
|
@@ -3437,6 +4425,7 @@ function createGeneratedContract(scope, apps = [
|
|
|
3437
4425
|
zephyrAgent: ZEPHYR_AGENT_VERSION,
|
|
3438
4426
|
wrangler: WRANGLER_VERSION
|
|
3439
4427
|
},
|
|
4428
|
+
cssFederation: createCssFederationContract(scope),
|
|
3440
4429
|
apps: apps.map((app)=>createAppGeneratedContract(scope, app, apps, enableTailwind))
|
|
3441
4430
|
};
|
|
3442
4431
|
}
|
|
@@ -3559,16 +4548,653 @@ function createTemplateManifest(modernVersion, packageSource) {
|
|
|
3559
4548
|
}
|
|
3560
4549
|
};
|
|
3561
4550
|
}
|
|
4551
|
+
function createAssertMfTypesScript(remotes = remoteApps) {
|
|
4552
|
+
return `import fs from 'node:fs';
|
|
4553
|
+
import path from 'node:path';
|
|
4554
|
+
|
|
4555
|
+
const root = process.cwd();
|
|
4556
|
+
const defaultAppDirs = ${JSON.stringify(remotes.map((remote)=>remote.directory), null, 2)};
|
|
4557
|
+
|
|
4558
|
+
const candidateDirs = process.argv.slice(2);
|
|
4559
|
+
const appDirs = candidateDirs.length
|
|
4560
|
+
? candidateDirs
|
|
4561
|
+
: fs.existsSync(path.join(root, 'module-federation.config.ts'))
|
|
4562
|
+
? ['.']
|
|
4563
|
+
: defaultAppDirs;
|
|
4564
|
+
|
|
4565
|
+
for (const appDir of appDirs) {
|
|
4566
|
+
const configPath = path.join(root, appDir, 'module-federation.config.ts');
|
|
4567
|
+
if (!fs.existsSync(configPath)) {
|
|
4568
|
+
throw new Error(
|
|
4569
|
+
\`Missing Module Federation config: \${path.relative(root, configPath)}\`,
|
|
4570
|
+
);
|
|
4571
|
+
}
|
|
4572
|
+
|
|
4573
|
+
const config = fs.readFileSync(configPath, 'utf-8');
|
|
4574
|
+
if (config.includes('dts: false')) {
|
|
4575
|
+
throw new Error(
|
|
4576
|
+
\`Module Federation DTS must stay enabled: \${path.relative(root, configPath)}\`,
|
|
4577
|
+
);
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
if (!config.includes("compilerInstance: '--package typescript -- tsc'")) {
|
|
4581
|
+
throw new Error(
|
|
4582
|
+
\`Module Federation DTS must use the workspace TypeScript compiler: \${path.relative(root, configPath)}\`,
|
|
4583
|
+
);
|
|
4584
|
+
}
|
|
4585
|
+
|
|
4586
|
+
if (!config.includes('exposes:')) {
|
|
4587
|
+
continue;
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4590
|
+
const typesArchivePath = path.join(root, appDir, 'dist/@mf-types.zip');
|
|
4591
|
+
if (!fs.existsSync(typesArchivePath)) {
|
|
4592
|
+
throw new Error(
|
|
4593
|
+
\`Missing Module Federation DTS archive: \${path.relative(root, typesArchivePath)}\`,
|
|
4594
|
+
);
|
|
4595
|
+
}
|
|
4596
|
+
|
|
4597
|
+
const stats = fs.statSync(typesArchivePath);
|
|
4598
|
+
if (stats.size === 0) {
|
|
4599
|
+
throw new Error(
|
|
4600
|
+
\`Empty Module Federation DTS archive: \${path.relative(root, typesArchivePath)}\`,
|
|
4601
|
+
);
|
|
4602
|
+
}
|
|
4603
|
+
}
|
|
4604
|
+
`;
|
|
4605
|
+
}
|
|
4606
|
+
function createWorkspaceValidationScript(scope, enableTailwind, remotes = remoteApps) {
|
|
4607
|
+
const verticals = remotes.filter(appHasEffectApi).map((remote)=>({
|
|
4608
|
+
id: remote.id,
|
|
4609
|
+
domain: remote.domain,
|
|
4610
|
+
stem: remote.effectApi.stem,
|
|
4611
|
+
group: serviceEffectGroupName(remote),
|
|
4612
|
+
path: remote.directory,
|
|
4613
|
+
mfName: remote.mfName,
|
|
4614
|
+
apiPrefix: remote.effectApi.prefix,
|
|
4615
|
+
packageName: ultramodern_workspace_packageName(scope, remote.packageSuffix),
|
|
4616
|
+
exposes: Object.keys(remote.exposes ?? {}),
|
|
4617
|
+
componentPaths: Object.keys(remote.exposes ?? {}).map((expose)=>remoteComponentOutputPath(remote, expose)).filter((componentPath)=>Boolean(componentPath)),
|
|
4618
|
+
namespace: appI18nNamespace(remote),
|
|
4619
|
+
routePagePaths: createRouteOwnedI18nPaths(remote).filter((route)=>'/' !== route.canonicalPath).map((route)=>createRoutePageFilePath(remote, route.canonicalPath)),
|
|
4620
|
+
localisedUrls: createLocalisedUrlsMap(remote),
|
|
4621
|
+
remoteRefs: remote.remoteRefs ?? []
|
|
4622
|
+
}));
|
|
4623
|
+
const shellNamespace = appI18nNamespace(shellApp);
|
|
4624
|
+
const oldRemotePaths = [
|
|
4625
|
+
'apps/remotes/remote-commerce',
|
|
4626
|
+
'apps/remotes/remote-identity',
|
|
4627
|
+
'apps/remotes/remote-design-system'
|
|
4628
|
+
];
|
|
4629
|
+
return `import { execFileSync } from 'node:child_process';
|
|
4630
|
+
import fs from 'node:fs';
|
|
4631
|
+
import path from 'node:path';
|
|
4632
|
+
|
|
4633
|
+
const root = process.cwd();
|
|
4634
|
+
const packageScope = '${scope}';
|
|
4635
|
+
const expectedPnpmVersion = '${PNPM_VERSION}';
|
|
4636
|
+
const tailwindEnabled = ${JSON.stringify(enableTailwind)};
|
|
4637
|
+
const fullStackVerticals = ${JSON.stringify(verticals, null, 2)};
|
|
4638
|
+
const shellNamespace = ${JSON.stringify(shellNamespace)};
|
|
4639
|
+
const oldRemotePaths = ${JSON.stringify(oldRemotePaths, null, 2)};
|
|
4640
|
+
|
|
4641
|
+
const readText = relativePath => fs.readFileSync(path.join(root, relativePath), 'utf-8');
|
|
4642
|
+
const readJson = relativePath => JSON.parse(readText(relativePath));
|
|
4643
|
+
const assert = (condition, message) => {
|
|
4644
|
+
if (!condition) {
|
|
4645
|
+
throw new Error(message);
|
|
4646
|
+
}
|
|
4647
|
+
};
|
|
4648
|
+
const assertExists = relativePath => {
|
|
4649
|
+
assert(fs.existsSync(path.join(root, relativePath)), \`Missing \${relativePath}\`);
|
|
4650
|
+
};
|
|
4651
|
+
const assertNotExists = relativePath => {
|
|
4652
|
+
assert(!fs.existsSync(path.join(root, relativePath)), \`Unexpected \${relativePath}\`);
|
|
4653
|
+
};
|
|
4654
|
+
const expectedWorkerName = packageSuffix => \`\${packageScope}-\${packageSuffix}\`.slice(0, 63);
|
|
4655
|
+
|
|
4656
|
+
const activePnpmVersion = execFileSync('pnpm', ['--version'], {
|
|
4657
|
+
cwd: root,
|
|
4658
|
+
encoding: 'utf-8',
|
|
4659
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
4660
|
+
}).trim();
|
|
4661
|
+
|
|
4662
|
+
assert(
|
|
4663
|
+
activePnpmVersion === expectedPnpmVersion,
|
|
4664
|
+
\`Generated workspace requires pnpm \${expectedPnpmVersion}; active pnpm is \${activePnpmVersion}. Run mise install, then rerun through mise exec -- pnpm ...\`,
|
|
4665
|
+
);
|
|
4666
|
+
|
|
4667
|
+
const requiredPaths = [
|
|
4668
|
+
'AGENTS.md',
|
|
4669
|
+
'.gitignore',
|
|
4670
|
+
'package.json',
|
|
4671
|
+
'pnpm-workspace.yaml',
|
|
4672
|
+
'tsconfig.base.json',
|
|
4673
|
+
'oxlint.config.ts',
|
|
4674
|
+
'oxfmt.config.ts',
|
|
4675
|
+
'.github/renovate.json',
|
|
4676
|
+
'.github/workflows/ultramodern-workspace-gates.yml',
|
|
4677
|
+
'.agents/skills-lock.json',
|
|
4678
|
+
'.agents/agent-reference-repos.json',
|
|
4679
|
+
'.agents/rstackjs-agent-skills-LICENSE',
|
|
4680
|
+
'topology/reference-topology.json',
|
|
4681
|
+
'topology/ownership.json',
|
|
4682
|
+
'topology/local-overlays/development.json',
|
|
4683
|
+
'.modernjs/ultramodern-workspace-template-manifest.json',
|
|
4684
|
+
'.modernjs/ultramodern-package-source.json',
|
|
4685
|
+
'.modernjs/ultramodern-generated-contract.json',
|
|
4686
|
+
'scripts/assert-mf-types.mjs',
|
|
4687
|
+
'scripts/bootstrap-agent-skills.mjs',
|
|
4688
|
+
'scripts/proof-cloudflare-version.mjs',
|
|
4689
|
+
'scripts/setup-agent-reference-repos.mjs',
|
|
4690
|
+
'apps/shell-super-app/package.json',
|
|
4691
|
+
'apps/shell-super-app/modern.config.ts',
|
|
4692
|
+
'apps/shell-super-app/module-federation.config.ts',
|
|
4693
|
+
'apps/shell-super-app/src/modern-app-env.d.ts',
|
|
4694
|
+
'apps/shell-super-app/src/modern.runtime.ts',
|
|
4695
|
+
'apps/shell-super-app/src/effect/recommendations-client.ts',
|
|
4696
|
+
'apps/shell-super-app/locales/en/translation.json',
|
|
4697
|
+
\`apps/shell-super-app/locales/en/\${shellNamespace}.json\`,
|
|
4698
|
+
'apps/shell-super-app/locales/cs/translation.json',
|
|
4699
|
+
\`apps/shell-super-app/locales/cs/\${shellNamespace}.json\`,
|
|
4700
|
+
'apps/shell-super-app/src/routes/index.css',
|
|
4701
|
+
'apps/shell-super-app/src/routes/layout.tsx',
|
|
4702
|
+
'apps/shell-super-app/src/routes/ultramodern-route-metadata.ts',
|
|
4703
|
+
'apps/shell-super-app/src/routes/[lang]/page.tsx',
|
|
4704
|
+
'packages/shared-contracts/src/index.ts',
|
|
4705
|
+
'packages/shared-design-tokens/src/index.ts',
|
|
4706
|
+
'packages/shared-design-tokens/src/tokens.css',
|
|
4707
|
+
'packages/shared-effect-api/src/index.ts',
|
|
4708
|
+
];
|
|
4709
|
+
|
|
4710
|
+
for (const vertical of fullStackVerticals) {
|
|
4711
|
+
requiredPaths.push(
|
|
4712
|
+
\`\${vertical.path}/package.json\`,
|
|
4713
|
+
\`\${vertical.path}/modern.config.ts\`,
|
|
4714
|
+
\`\${vertical.path}/module-federation.config.ts\`,
|
|
4715
|
+
\`\${vertical.path}/api/effect/index.ts\`,
|
|
4716
|
+
\`\${vertical.path}/shared/effect/api.ts\`,
|
|
4717
|
+
\`\${vertical.path}/src/effect/\${vertical.stem}-client.ts\`,
|
|
4718
|
+
\`\${vertical.path}/src/modern-app-env.d.ts\`,
|
|
4719
|
+
\`\${vertical.path}/src/modern.runtime.ts\`,
|
|
4720
|
+
\`\${vertical.path}/src/remote-entry.tsx\`,
|
|
4721
|
+
...vertical.componentPaths,
|
|
4722
|
+
\`\${vertical.path}/locales/en/translation.json\`,
|
|
4723
|
+
\`\${vertical.path}/locales/en/\${vertical.namespace}.json\`,
|
|
4724
|
+
\`\${vertical.path}/locales/cs/translation.json\`,
|
|
4725
|
+
\`\${vertical.path}/locales/cs/\${vertical.namespace}.json\`,
|
|
4726
|
+
\`\${vertical.path}/src/routes/index.css\`,
|
|
4727
|
+
\`\${vertical.path}/src/routes/layout.tsx\`,
|
|
4728
|
+
\`\${vertical.path}/src/routes/ultramodern-route-metadata.ts\`,
|
|
4729
|
+
\`\${vertical.path}/src/routes/[lang]/page.tsx\`,
|
|
4730
|
+
...vertical.routePagePaths,
|
|
4731
|
+
);
|
|
4732
|
+
}
|
|
4733
|
+
|
|
4734
|
+
if (tailwindEnabled) {
|
|
4735
|
+
requiredPaths.push(
|
|
4736
|
+
'apps/shell-super-app/postcss.config.mjs',
|
|
4737
|
+
'apps/shell-super-app/tailwind.config.ts',
|
|
4738
|
+
...fullStackVerticals.flatMap(vertical => [
|
|
4739
|
+
\`\${vertical.path}/postcss.config.mjs\`,
|
|
4740
|
+
\`\${vertical.path}/tailwind.config.ts\`,
|
|
4741
|
+
]),
|
|
4742
|
+
);
|
|
4743
|
+
}
|
|
4744
|
+
|
|
4745
|
+
for (const requiredPath of requiredPaths) {
|
|
4746
|
+
assertExists(requiredPath);
|
|
4747
|
+
}
|
|
4748
|
+
for (const oldRemotePath of oldRemotePaths) {
|
|
4749
|
+
assertNotExists(oldRemotePath);
|
|
4750
|
+
}
|
|
4751
|
+
assertNotExists('services/service-recommendations-effect');
|
|
4752
|
+
|
|
4753
|
+
const rootPackage = readJson('package.json');
|
|
4754
|
+
const packageSource = readJson('.modernjs/ultramodern-package-source.json');
|
|
4755
|
+
const generatedContract = readJson('.modernjs/ultramodern-generated-contract.json');
|
|
4756
|
+
const topology = readJson('topology/reference-topology.json');
|
|
4757
|
+
const ownership = readJson('topology/ownership.json');
|
|
4758
|
+
const overlay = readJson('topology/local-overlays/development.json');
|
|
4759
|
+
|
|
4760
|
+
assert(rootPackage.private === true, 'Root package must be private');
|
|
4761
|
+
assert(rootPackage.packageManager === \`pnpm@\${expectedPnpmVersion}\`, 'Root must pin pnpm');
|
|
4762
|
+
assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare presetUltramodern');
|
|
4763
|
+
assert(rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json', 'Root must point at package source metadata');
|
|
4764
|
+
assert(rootPackage.modernjs?.packageSource?.strategy === packageSource.strategy, 'Root package source strategy must match metadata');
|
|
4765
|
+
assert(packageSource.strategy === 'workspace' || packageSource.strategy === 'install', 'Package source strategy must be workspace or install');
|
|
4766
|
+
assert(packageSource.generatedWorkspacePackages?.specifier === 'workspace:*', 'Generated workspace packages must keep workspace:* links');
|
|
4767
|
+
assert(
|
|
4768
|
+
rootPackage.scripts?.build ===
|
|
4769
|
+
'pnpm -r --filter "./apps/remotes/**" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
|
|
4770
|
+
'Root build script must build remotes before shell',
|
|
4771
|
+
);
|
|
4772
|
+
assert(rootPackage.scripts?.['ultramodern:check'] === 'node ./scripts/validate-ultramodern-workspace.mjs', 'Root must expose ultramodern:check');
|
|
4773
|
+
assert(rootPackage.scripts?.['ultramodern:assert-mf-types'] === 'node ./scripts/assert-mf-types.mjs', 'Root must expose ultramodern:assert-mf-types');
|
|
4774
|
+
assert(rootPackage.scripts?.['cloudflare:deploy']?.includes('run cloudflare:deploy'), 'Root must expose cloudflare:deploy');
|
|
4775
|
+
assert(rootPackage.scripts?.['cloudflare:proof'] === 'node ./scripts/proof-cloudflare-version.mjs --out .codex/reports/cloudflare-version-proof/public-url-proof.json', 'Root must expose cloudflare:proof');
|
|
4776
|
+
|
|
4777
|
+
const expectedAppIds = ['shell-super-app', ...fullStackVerticals.map(vertical => vertical.id)];
|
|
4778
|
+
assert(
|
|
4779
|
+
JSON.stringify(generatedContract.apps?.map(app => app.id)) === JSON.stringify(expectedAppIds),
|
|
4780
|
+
'Generated contract must contain shell plus the Tractor full-stack remotes',
|
|
4781
|
+
);
|
|
4782
|
+
assert(generatedContract.cssFederation?.sharedDesignTokens?.owner?.id === 'shared-design-tokens', 'CSS federation must declare shared design token ownership');
|
|
4783
|
+
assert(generatedContract.cssFederation?.sharedDesignTokens?.role === 'shared-design-tokens', 'CSS federation must mark shared-design-tokens as token owner');
|
|
4784
|
+
assert(generatedContract.cssFederation?.sharedDesignTokens?.rootSelector === ':root', 'Shared design tokens must declare their root selector');
|
|
4785
|
+
assert(generatedContract.cssFederation?.sharedDesignTokens?.classPrefix === '--um-', 'Shared design tokens must declare their CSS custom property prefix');
|
|
4786
|
+
assert(generatedContract.cssFederation?.sharedDesignTokens?.layers?.owned?.includes('ultramodern-shared-tokens'), 'Shared design tokens must own the shared token CSS layer');
|
|
4787
|
+
assert(generatedContract.cssFederation?.sharedDesignTokens?.entrypoints?.css?.includes('packages/shared-design-tokens/src/tokens.css'), 'Shared design tokens must declare their CSS entrypoint');
|
|
4788
|
+
assert(generatedContract.cssFederation?.sharedDesignTokens?.assets?.exports?.includes('./tokens.css'), 'Shared design tokens must export their CSS asset');
|
|
4789
|
+
assert(generatedContract.cssFederation?.sharedDesignTokens?.dedupe?.duplicateBaseStylesAllowed === false, 'Shared design token CSS must be deduplicated');
|
|
4790
|
+
assert(generatedContract.cssFederation?.sharedDesignTokens?.ssr?.firstPaintRequired === true, 'Shared design token CSS must be required for SSR first paint');
|
|
4791
|
+
|
|
4792
|
+
const shellPackage = readJson('apps/shell-super-app/package.json');
|
|
4793
|
+
const expectedZephyrDependencies = Object.fromEntries(
|
|
4794
|
+
fullStackVerticals.map(vertical => [
|
|
4795
|
+
vertical.domain,
|
|
4796
|
+
\`\${vertical.packageName}@workspace:*\`,
|
|
4797
|
+
]),
|
|
4798
|
+
);
|
|
4799
|
+
assert(
|
|
4800
|
+
JSON.stringify(shellPackage['zephyr:dependencies']) ===
|
|
4801
|
+
JSON.stringify(expectedZephyrDependencies),
|
|
4802
|
+
'Shell Zephyr dependencies must reference every Tractor remote package',
|
|
4803
|
+
);
|
|
4804
|
+
const shellContract = generatedContract.apps?.find(app => app.id === 'shell-super-app');
|
|
4805
|
+
assert(shellContract?.deploy?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell Cloudflare workerName is incorrect');
|
|
4806
|
+
assert(shellContract?.deploy?.cloudflare?.publicUrlEnv === 'ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP', 'Shell Cloudflare public URL env is incorrect');
|
|
4807
|
+
assert(topology.shell?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell topology Cloudflare workerName is incorrect');
|
|
4808
|
+
assert(shellContract?.styling?.federation?.owner?.id === 'shell-super-app', 'Shell CSS federation owner is missing');
|
|
4809
|
+
assert(shellContract?.styling?.federation?.role === 'shell-base-overlay', 'Shell must own base and overlay CSS');
|
|
4810
|
+
assert(shellContract?.styling?.federation?.rootSelector === '[data-app-id="shell-super-app"]', 'Shell CSS root selector is incorrect');
|
|
4811
|
+
assert(shellContract?.styling?.federation?.classPrefix === 'shell-', 'Shell CSS class prefix is incorrect');
|
|
4812
|
+
assert(shellContract?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-base'), 'Shell must own the base CSS layer');
|
|
4813
|
+
assert(shellContract?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-overlay'), 'Shell must own the overlay CSS layer');
|
|
4814
|
+
assert(shellContract?.styling?.federation?.entrypoints?.css?.includes('src/routes/index.css'), 'Shell CSS entrypoint is missing');
|
|
4815
|
+
assert(shellContract?.styling?.federation?.assets?.shared?.some(asset => asset.endsWith('/shared-design-tokens/tokens.css')), 'Shell must import the shared design token CSS asset');
|
|
4816
|
+
assert(shellContract?.styling?.federation?.dedupe?.duplicateBaseStylesAllowed === false, 'Shell CSS contract must forbid duplicated base styles');
|
|
4817
|
+
assert(shellContract?.styling?.federation?.ssr?.firstPaintRequired === true, 'Shell CSS must be required for SSR first paint');
|
|
4818
|
+
assert(
|
|
4819
|
+
topology.shell?.remoteRefs?.join(',') === fullStackVerticals.map(vertical => vertical.id).join(','),
|
|
4820
|
+
'Topology shell remoteRefs must match Tractor remotes',
|
|
4821
|
+
);
|
|
4822
|
+
assert(topology.remotes?.length === fullStackVerticals.length, 'Topology must contain only Tractor remotes');
|
|
4823
|
+
assert((topology.effectServices ?? []).length === 0, 'Default APIs must be vertical-owned, not effectServices');
|
|
4824
|
+
|
|
4825
|
+
for (const vertical of fullStackVerticals) {
|
|
4826
|
+
const packageJson = readJson(\`\${vertical.path}/package.json\`);
|
|
4827
|
+
assert(packageJson.name === vertical.packageName, \`\${vertical.id} package name is incorrect\`);
|
|
4828
|
+
assert(packageJson.scripts?.['cloudflare:deploy'] === 'MODERNJS_DEPLOY=cloudflare modern deploy', \`\${vertical.id} must expose cloudflare:deploy\`);
|
|
4829
|
+
assert(packageJson.scripts?.['cloudflare:proof']?.includes(\`--app \${vertical.id}\`), \`\${vertical.id} must expose cloudflare:proof\`);
|
|
4830
|
+
assert(packageJson.dependencies?.['@modern-js/plugin-bff'], \`\${vertical.id} must depend on plugin-bff\`);
|
|
4831
|
+
assert(packageJson.exports?.['./effect/client'] === \`./src/effect/\${vertical.stem}-client.ts\`, \`\${vertical.id} must export its Effect client\`);
|
|
4832
|
+
assert(packageJson.exports?.['./shared/effect/api'] === './shared/effect/api.ts', \`\${vertical.id} must export its Effect API contract\`);
|
|
4833
|
+
const expectedVerticalZephyrDependencies = Object.fromEntries(
|
|
4834
|
+
fullStackVerticals
|
|
4835
|
+
.filter(candidate => vertical.remoteRefs.includes(candidate.id))
|
|
4836
|
+
.map(candidate => [
|
|
4837
|
+
candidate.domain,
|
|
4838
|
+
\`\${candidate.packageName}@workspace:*\`,
|
|
4839
|
+
]),
|
|
4840
|
+
);
|
|
4841
|
+
assert(
|
|
4842
|
+
JSON.stringify(packageJson['zephyr:dependencies']) ===
|
|
4843
|
+
JSON.stringify(expectedVerticalZephyrDependencies),
|
|
4844
|
+
\`\${vertical.id} Zephyr dependencies must match declared MF remote refs\`,
|
|
4845
|
+
);
|
|
4846
|
+
|
|
4847
|
+
const contractEntry = generatedContract.apps?.find(app => app.id === vertical.id);
|
|
4848
|
+
assert(contractEntry?.path === vertical.path, \`\${vertical.id} generated contract path is incorrect\`);
|
|
4849
|
+
assert(contractEntry?.kind === 'vertical', \`\${vertical.id} generated contract kind is incorrect\`);
|
|
4850
|
+
assert(contractEntry?.deploy?.cloudflare?.workerName === expectedWorkerName(vertical.id), \`\${vertical.id} Cloudflare workerName is incorrect\`);
|
|
4851
|
+
assert(contractEntry?.deploy?.cloudflare?.publicUrlEnv === \`ULTRAMODERN_PUBLIC_URL_\${vertical.id.replace(/-/g, '_').toUpperCase()}\`, \`\${vertical.id} Cloudflare public URL env is incorrect\`);
|
|
4852
|
+
assert(contractEntry?.deploy?.cloudflare?.routes?.effectReadiness === \`\${vertical.apiPrefix}/effect/\${vertical.stem}/readiness\`, \`\${vertical.id} Cloudflare proof readiness route is incorrect\`);
|
|
4853
|
+
assert(contractEntry?.moduleFederation?.name === vertical.mfName, \`\${vertical.id} MF name is incorrect\`);
|
|
4854
|
+
assert(JSON.stringify(contractEntry?.moduleFederation?.exposes) === JSON.stringify(vertical.exposes), \`\${vertical.id} MF exposes are incorrect\`);
|
|
4855
|
+
assert(contractEntry?.moduleFederation?.dts?.compilerInstance === '--package typescript -- tsc', \`\${vertical.id} must keep mandatory DTS compiler\`);
|
|
4856
|
+
assert(JSON.stringify(contractEntry?.moduleFederation?.remoteRefs ?? []) === JSON.stringify(vertical.remoteRefs), \`\${vertical.id} MF remoteRefs are incorrect\`);
|
|
4857
|
+
assert(
|
|
4858
|
+
JSON.stringify((contractEntry?.moduleFederation?.remotes ?? []).map(remote => remote.id)) ===
|
|
4859
|
+
JSON.stringify(vertical.remoteRefs),
|
|
4860
|
+
\`\${vertical.id} MF consumed remotes are incorrect\`,
|
|
4861
|
+
);
|
|
4862
|
+
assert(contractEntry?.effect?.prefix === vertical.apiPrefix, \`\${vertical.id} Effect API prefix is incorrect\`);
|
|
4863
|
+
assert(contractEntry?.effect?.group === vertical.group, \`\${vertical.id} Effect group is incorrect\`);
|
|
4864
|
+
assert(contractEntry?.effect?.readiness?.endpoint === \`/effect/\${vertical.stem}/readiness\`, \`\${vertical.id} readiness endpoint is incorrect\`);
|
|
4865
|
+
assert(contractEntry?.effect?.operations?.readiness?.path === \`/effect/\${vertical.stem}/readiness\`, \`\${vertical.id} readiness operation is missing\`);
|
|
4866
|
+
assert(contractEntry?.effect?.requestContext?.propagatedHeaders?.includes('traceparent'), \`\${vertical.id} trace context propagation is missing\`);
|
|
4867
|
+
assert(Object.keys(contractEntry?.effect?.domainOperations ?? {}).length >= 3, \`\${vertical.id} domain operations are missing\`);
|
|
4868
|
+
assert(contractEntry?.i18n?.languages?.includes('en') && contractEntry?.i18n?.languages?.includes('cs'), \`\${vertical.id} must declare i18n languages\`);
|
|
4869
|
+
assert(contractEntry?.i18n?.namespace === vertical.namespace, \`\${vertical.id} i18n namespace is incorrect\`);
|
|
4870
|
+
assert(
|
|
4871
|
+
JSON.stringify(contractEntry?.i18n?.localisedUrls) === JSON.stringify(vertical.localisedUrls),
|
|
4872
|
+
\`\${vertical.id} localisedUrls must come from route metadata\`,
|
|
4873
|
+
);
|
|
4874
|
+
assert(contractEntry?.routes?.source === 'route-owned', \`\${vertical.id} routes must be route-owned\`);
|
|
4875
|
+
assert(contractEntry?.routes?.metadataExport === './src/routes/ultramodern-route-metadata', \`\${vertical.id} route metadata export is incorrect\`);
|
|
4876
|
+
assert(contractEntry?.styling?.federation?.owner?.id === vertical.id, \`\${vertical.id} CSS federation owner is missing\`);
|
|
4877
|
+
assert(contractEntry?.styling?.federation?.role === 'vertical-remote-css', \`\${vertical.id} must own only vertical CSS\`);
|
|
4878
|
+
assert(contractEntry?.styling?.federation?.rootSelector === \`[data-app-id="\${vertical.id}"]\`, \`\${vertical.id} CSS root selector is incorrect\`);
|
|
4879
|
+
assert(contractEntry?.styling?.federation?.classPrefix === \`\${vertical.domain}-\`, \`\${vertical.id} CSS class prefix is incorrect\`);
|
|
4880
|
+
assert(contractEntry?.styling?.federation?.layers?.owned?.includes(\`ultramodern-remote-\${vertical.domain}\`), \`\${vertical.id} remote CSS layer is missing\`);
|
|
4881
|
+
assert(!contractEntry?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-base'), \`\${vertical.id} must not own shell base CSS\`);
|
|
4882
|
+
assert(contractEntry?.styling?.federation?.entrypoints?.remoteEntry === 'src/remote-entry.tsx', \`\${vertical.id} remote CSS contract must include remote entry\`);
|
|
4883
|
+
assert(contractEntry?.styling?.federation?.assets?.shared?.some(asset => asset.endsWith('/shared-design-tokens/tokens.css')), \`\${vertical.id} must import shared design token CSS\`);
|
|
4884
|
+
assert(contractEntry?.styling?.federation?.dedupe?.runtimeLoad === 'once-per-content-hash', \`\${vertical.id} CSS dedupe strategy is incorrect\`);
|
|
4885
|
+
assert(contractEntry?.styling?.federation?.ssr?.remoteCss === 'remote-manifest-owned-css', \`\${vertical.id} SSR CSS loading contract is incorrect\`);
|
|
4886
|
+
|
|
4887
|
+
const topologyEntry = topology.remotes?.find(remote => remote.id === vertical.id);
|
|
4888
|
+
assert(topologyEntry?.kind === 'vertical', \`\${vertical.id} topology kind is incorrect\`);
|
|
4889
|
+
assert(topologyEntry?.package === vertical.packageName, \`\${vertical.id} topology package is incorrect\`);
|
|
4890
|
+
assert(topologyEntry?.cloudflare?.workerName === expectedWorkerName(vertical.id), \`\${vertical.id} topology Cloudflare workerName is incorrect\`);
|
|
4891
|
+
assert(topologyEntry?.moduleFederation?.name === vertical.mfName, \`\${vertical.id} topology MF name is incorrect\`);
|
|
4892
|
+
assert(JSON.stringify(topologyEntry?.moduleFederation?.exposes) === JSON.stringify(vertical.exposes), \`\${vertical.id} topology exposes are incorrect\`);
|
|
4893
|
+
assert(JSON.stringify(topologyEntry?.moduleFederation?.remoteRefs ?? []) === JSON.stringify(vertical.remoteRefs), \`\${vertical.id} topology remoteRefs are incorrect\`);
|
|
4894
|
+
assert(topologyEntry?.api?.effect?.bff?.prefix === vertical.apiPrefix, \`\${vertical.id} topology API prefix is incorrect\`);
|
|
4895
|
+
assert(topologyEntry?.api?.effect?.serverEntry === \`\${vertical.path}/api/effect/index.ts\`, \`\${vertical.id} topology server entry is incorrect\`);
|
|
4896
|
+
assert(topologyEntry?.api?.effect?.readiness?.endpoint === \`/effect/\${vertical.stem}/readiness\`, \`\${vertical.id} topology readiness endpoint is incorrect\`);
|
|
4897
|
+
assert(Object.keys(topologyEntry?.api?.effect?.domainOperations ?? {}).length >= 3, \`\${vertical.id} topology domain operations are missing\`);
|
|
4898
|
+
|
|
4899
|
+
assert(ownership.owners?.some(owner => owner.id === vertical.id && owner.path === vertical.path), \`\${vertical.id} ownership entry is missing\`);
|
|
4900
|
+
assert(overlay.ports?.[vertical.id], \`\${vertical.id} development port is missing\`);
|
|
4901
|
+
assert(overlay.manifests?.[vertical.id]?.includes('/mf-manifest.json'), \`\${vertical.id} development manifest is missing\`);
|
|
4902
|
+
assert(overlay.apis?.[vertical.id]?.endsWith(vertical.apiPrefix), \`\${vertical.id} development API URL is missing\`);
|
|
4903
|
+
}
|
|
4904
|
+
|
|
4905
|
+
console.log('UltraModern workspace scaffold validated');
|
|
4906
|
+
`;
|
|
4907
|
+
}
|
|
4908
|
+
function createCloudflareVersionProofScript() {
|
|
4909
|
+
return `#!/usr/bin/env node
|
|
4910
|
+
import fs from 'node:fs';
|
|
4911
|
+
import path from 'node:path';
|
|
4912
|
+
|
|
4913
|
+
const contractPath = '.modernjs/ultramodern-generated-contract.json';
|
|
4914
|
+
const defaultOut =
|
|
4915
|
+
'.codex/reports/cloudflare-version-proof/public-url-proof.json';
|
|
4916
|
+
|
|
4917
|
+
function readJson(relativePath) {
|
|
4918
|
+
return JSON.parse(fs.readFileSync(path.resolve(relativePath), 'utf8'));
|
|
4919
|
+
}
|
|
4920
|
+
|
|
4921
|
+
function parseArgs(argv) {
|
|
4922
|
+
const parsed = {
|
|
4923
|
+
appId: undefined,
|
|
4924
|
+
out: defaultOut,
|
|
4925
|
+
requirePublicUrls: false,
|
|
4926
|
+
};
|
|
4927
|
+
|
|
4928
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
4929
|
+
const arg = argv[index];
|
|
4930
|
+
if (arg === '--app') {
|
|
4931
|
+
parsed.appId = argv[index + 1];
|
|
4932
|
+
index += 1;
|
|
4933
|
+
} else if (arg === '--out') {
|
|
4934
|
+
parsed.out = argv[index + 1];
|
|
4935
|
+
index += 1;
|
|
4936
|
+
} else if (arg === '--require-public-urls') {
|
|
4937
|
+
parsed.requirePublicUrls = true;
|
|
4938
|
+
} else if (arg === '--help' || arg === '-h') {
|
|
4939
|
+
parsed.help = true;
|
|
4940
|
+
} else {
|
|
4941
|
+
throw new Error(\`Unknown argument: \${arg}\`);
|
|
4942
|
+
}
|
|
4943
|
+
}
|
|
4944
|
+
|
|
4945
|
+
return parsed;
|
|
4946
|
+
}
|
|
4947
|
+
|
|
4948
|
+
function printHelp() {
|
|
4949
|
+
process.stdout.write(\`Usage:
|
|
4950
|
+
node scripts/proof-cloudflare-version.mjs [--app remote-explore] [--out evidence.json] [--require-public-urls]
|
|
4951
|
+
|
|
4952
|
+
Set each app's public URL using the contract env key, for example:
|
|
4953
|
+
ULTRAMODERN_PUBLIC_URL_REMOTE_EXPLORE=https://remote-explore.example.workers.dev
|
|
4954
|
+
\`);
|
|
4955
|
+
}
|
|
4956
|
+
|
|
4957
|
+
function joinUrl(baseUrl, routePath) {
|
|
4958
|
+
return new URL(routePath, baseUrl.endsWith('/') ? baseUrl : \`\${baseUrl}/\`);
|
|
4959
|
+
}
|
|
4960
|
+
|
|
4961
|
+
async function fetchText(url) {
|
|
4962
|
+
const response = await fetch(url);
|
|
4963
|
+
return {
|
|
4964
|
+
ok: response.ok,
|
|
4965
|
+
status: response.status,
|
|
4966
|
+
contentType: response.headers.get('content-type'),
|
|
4967
|
+
body: await response.text(),
|
|
4968
|
+
};
|
|
4969
|
+
}
|
|
4970
|
+
|
|
4971
|
+
function parseMaybeJson(body) {
|
|
4972
|
+
try {
|
|
4973
|
+
return JSON.parse(body);
|
|
4974
|
+
} catch {
|
|
4975
|
+
return undefined;
|
|
4976
|
+
}
|
|
4977
|
+
}
|
|
4978
|
+
|
|
4979
|
+
function markerFromJson(value) {
|
|
4980
|
+
if (!value || typeof value !== 'object') {
|
|
4981
|
+
return undefined;
|
|
4982
|
+
}
|
|
4983
|
+
if (value.marker && typeof value.marker.build === 'string') {
|
|
4984
|
+
return value.marker.build;
|
|
4985
|
+
}
|
|
4986
|
+
if (typeof value.build === 'string') {
|
|
4987
|
+
return value.build;
|
|
4988
|
+
}
|
|
4989
|
+
for (const nested of Object.values(value)) {
|
|
4990
|
+
if (Array.isArray(nested)) {
|
|
4991
|
+
for (const item of nested) {
|
|
4992
|
+
const marker = markerFromJson(item);
|
|
4993
|
+
if (marker) {
|
|
4994
|
+
return marker;
|
|
4995
|
+
}
|
|
4996
|
+
}
|
|
4997
|
+
} else {
|
|
4998
|
+
const marker = markerFromJson(nested);
|
|
4999
|
+
if (marker) {
|
|
5000
|
+
return marker;
|
|
5001
|
+
}
|
|
5002
|
+
}
|
|
5003
|
+
}
|
|
5004
|
+
return undefined;
|
|
5005
|
+
}
|
|
5006
|
+
|
|
5007
|
+
function extractUiMarker(html) {
|
|
5008
|
+
return html.match(/data-build-marker=["']([^"']+)["']/u)?.[1];
|
|
5009
|
+
}
|
|
5010
|
+
|
|
5011
|
+
function assert(condition, message) {
|
|
5012
|
+
if (!condition) {
|
|
5013
|
+
throw new Error(message);
|
|
5014
|
+
}
|
|
5015
|
+
}
|
|
5016
|
+
|
|
5017
|
+
async function validateApp(app, publicUrl) {
|
|
5018
|
+
const cloudflare = app.deploy?.cloudflare;
|
|
5019
|
+
const routes = cloudflare?.routes ?? {};
|
|
5020
|
+
const evidence = {
|
|
5021
|
+
appId: app.id,
|
|
5022
|
+
publicUrl,
|
|
5023
|
+
workerName: cloudflare?.workerName,
|
|
5024
|
+
publicUrlEnv: cloudflare?.publicUrlEnv,
|
|
5025
|
+
assertions: [],
|
|
5026
|
+
};
|
|
5027
|
+
|
|
5028
|
+
const ssrRoute = routes.ssr ?? '/en';
|
|
5029
|
+
const ssr = await fetchText(joinUrl(publicUrl, ssrRoute));
|
|
5030
|
+
evidence.assertions.push({
|
|
5031
|
+
type: 'ssr',
|
|
5032
|
+
route: ssrRoute,
|
|
5033
|
+
status: ssr.ok ? 'pass' : 'fail',
|
|
5034
|
+
statusCode: ssr.status,
|
|
5035
|
+
});
|
|
5036
|
+
assert(ssr.ok, \`\${app.id} SSR route returned HTTP \${ssr.status}\`);
|
|
5037
|
+
|
|
5038
|
+
const uiMarker = extractUiMarker(ssr.body);
|
|
5039
|
+
evidence.assertions.push({
|
|
5040
|
+
type: 'ui-marker',
|
|
5041
|
+
expected: app.marker?.build,
|
|
5042
|
+
actual: uiMarker,
|
|
5043
|
+
status: uiMarker === app.marker?.build ? 'pass' : 'fail',
|
|
5044
|
+
});
|
|
5045
|
+
assert(uiMarker === app.marker?.build, \`\${app.id} UI marker mismatch\`);
|
|
5046
|
+
|
|
5047
|
+
const cssRootSelector = app.styling?.federation?.rootSelector;
|
|
5048
|
+
const expectedAppId = cssRootSelector?.match(/data-app-id="([^"]+)"/u)?.[1];
|
|
5049
|
+
evidence.assertions.push({
|
|
5050
|
+
type: 'css-root-marker',
|
|
5051
|
+
expected: cssRootSelector,
|
|
5052
|
+
status:
|
|
5053
|
+
expectedAppId && ssr.body.includes(\`data-app-id="\${expectedAppId}"\`)
|
|
5054
|
+
? 'pass'
|
|
5055
|
+
: 'fail',
|
|
5056
|
+
});
|
|
5057
|
+
assert(
|
|
5058
|
+
expectedAppId && ssr.body.includes(\`data-app-id="\${expectedAppId}"\`),
|
|
5059
|
+
\`\${app.id} SSR response is missing CSS root marker \${cssRootSelector}\`,
|
|
5060
|
+
);
|
|
5061
|
+
|
|
5062
|
+
const manifestRoute = routes.mfManifest ?? '/mf-manifest.json';
|
|
5063
|
+
const manifest = await fetchText(joinUrl(publicUrl, manifestRoute));
|
|
5064
|
+
evidence.assertions.push({
|
|
5065
|
+
type: 'mf-manifest',
|
|
5066
|
+
route: manifestRoute,
|
|
5067
|
+
status: manifest.ok ? 'pass' : 'fail',
|
|
5068
|
+
statusCode: manifest.status,
|
|
5069
|
+
});
|
|
5070
|
+
assert(
|
|
5071
|
+
manifest.ok,
|
|
5072
|
+
\`\${app.id} MF manifest returned HTTP \${manifest.status}\`,
|
|
5073
|
+
);
|
|
5074
|
+
|
|
5075
|
+
const localeRoute = routes.locale ?? \`/locales/en/\${app.i18n?.namespace}.json\`;
|
|
5076
|
+
const locale = await fetchText(joinUrl(publicUrl, localeRoute));
|
|
5077
|
+
const localeJson = parseMaybeJson(locale.body);
|
|
5078
|
+
evidence.assertions.push({
|
|
5079
|
+
type: 'i18n-marker',
|
|
5080
|
+
namespace: app.i18n?.namespace,
|
|
5081
|
+
route: localeRoute,
|
|
5082
|
+
status:
|
|
5083
|
+
locale.ok &&
|
|
5084
|
+
localeJson &&
|
|
5085
|
+
Object.hasOwn(localeJson, app.i18n?.namespace)
|
|
5086
|
+
? 'pass'
|
|
5087
|
+
: 'fail',
|
|
5088
|
+
statusCode: locale.status,
|
|
5089
|
+
});
|
|
5090
|
+
assert(locale.ok, \`\${app.id} locale JSON returned HTTP \${locale.status}\`);
|
|
5091
|
+
assert(
|
|
5092
|
+
localeJson && Object.hasOwn(localeJson, app.i18n?.namespace),
|
|
5093
|
+
\`\${app.id} locale JSON is missing namespace \${app.i18n?.namespace}\`,
|
|
5094
|
+
);
|
|
5095
|
+
|
|
5096
|
+
if (routes.effectReadiness) {
|
|
5097
|
+
const readiness = await fetchText(joinUrl(publicUrl, routes.effectReadiness));
|
|
5098
|
+
const readinessJson = parseMaybeJson(readiness.body);
|
|
5099
|
+
const apiMarker = markerFromJson(readinessJson);
|
|
5100
|
+
evidence.assertions.push({
|
|
5101
|
+
type: 'api-marker',
|
|
5102
|
+
route: routes.effectReadiness,
|
|
5103
|
+
expected: app.marker?.build,
|
|
5104
|
+
actual: apiMarker,
|
|
5105
|
+
status: readiness.ok && apiMarker === app.marker?.build ? 'pass' : 'fail',
|
|
5106
|
+
statusCode: readiness.status,
|
|
5107
|
+
});
|
|
5108
|
+
assert(
|
|
5109
|
+
readiness.ok,
|
|
5110
|
+
\`\${app.id} Effect readiness returned HTTP \${readiness.status}\`,
|
|
5111
|
+
);
|
|
5112
|
+
assert(apiMarker === app.marker?.build, \`\${app.id} API marker mismatch\`);
|
|
5113
|
+
}
|
|
5114
|
+
|
|
5115
|
+
return evidence;
|
|
5116
|
+
}
|
|
5117
|
+
|
|
5118
|
+
async function main(argv = process.argv.slice(2)) {
|
|
5119
|
+
const args = parseArgs(argv);
|
|
5120
|
+
if (args.help) {
|
|
5121
|
+
printHelp();
|
|
5122
|
+
return 0;
|
|
5123
|
+
}
|
|
5124
|
+
|
|
5125
|
+
const contract = readJson(contractPath);
|
|
5126
|
+
const apps = args.appId
|
|
5127
|
+
? contract.apps.filter(app => app.id === args.appId)
|
|
5128
|
+
: contract.apps;
|
|
5129
|
+
assert(apps.length > 0, \`No generated app matched \${args.appId}\`);
|
|
5130
|
+
|
|
5131
|
+
const results = [];
|
|
5132
|
+
const skipped = [];
|
|
5133
|
+
for (const app of apps) {
|
|
5134
|
+
const publicUrlEnv = app.deploy?.cloudflare?.publicUrlEnv;
|
|
5135
|
+
const publicUrl = publicUrlEnv && process.env[publicUrlEnv];
|
|
5136
|
+
if (!publicUrl) {
|
|
5137
|
+
const skippedEntry = {
|
|
5138
|
+
appId: app.id,
|
|
5139
|
+
status: args.requirePublicUrls ? 'fail' : 'skipped',
|
|
5140
|
+
publicUrlEnv,
|
|
5141
|
+
reason: 'public URL environment variable is not set',
|
|
5142
|
+
};
|
|
5143
|
+
skipped.push(skippedEntry);
|
|
5144
|
+
if (args.requirePublicUrls) {
|
|
5145
|
+
throw new Error(\`\${app.id} requires \${publicUrlEnv}\`);
|
|
5146
|
+
}
|
|
5147
|
+
continue;
|
|
5148
|
+
}
|
|
5149
|
+
results.push(await validateApp(app, publicUrl));
|
|
5150
|
+
}
|
|
5151
|
+
|
|
5152
|
+
const report = {
|
|
5153
|
+
schemaVersion: 1,
|
|
5154
|
+
generatedAt: new Date().toISOString(),
|
|
5155
|
+
status: results.length > 0 ? 'pass' : 'skipped',
|
|
5156
|
+
contractPath,
|
|
5157
|
+
results,
|
|
5158
|
+
skipped,
|
|
5159
|
+
};
|
|
5160
|
+
|
|
5161
|
+
fs.mkdirSync(path.dirname(args.out), { recursive: true });
|
|
5162
|
+
fs.writeFileSync(args.out, \`\${JSON.stringify(report, null, 2)}\\n\`);
|
|
5163
|
+
process.stdout.write(
|
|
5164
|
+
\`[cloudflare-version-proof] \${report.status}: \${args.out}\\n\`,
|
|
5165
|
+
);
|
|
5166
|
+
return 0;
|
|
5167
|
+
}
|
|
5168
|
+
|
|
5169
|
+
main().then(
|
|
5170
|
+
exitCode => {
|
|
5171
|
+
process.exitCode = exitCode;
|
|
5172
|
+
},
|
|
5173
|
+
error => {
|
|
5174
|
+
process.stderr.write(\`[cloudflare-version-proof] \${error.message}\\n\`);
|
|
5175
|
+
process.exitCode = 1;
|
|
5176
|
+
},
|
|
5177
|
+
);
|
|
5178
|
+
`;
|
|
5179
|
+
}
|
|
5180
|
+
function writeGeneratedWorkspaceScripts(targetDir, scope, enableTailwind) {
|
|
5181
|
+
writeFileReplacing(targetDir, "scripts/assert-mf-types.mjs", createAssertMfTypesScript());
|
|
5182
|
+
writeFileReplacing(targetDir, "scripts/validate-ultramodern-workspace.mjs", createWorkspaceValidationScript(scope, enableTailwind));
|
|
5183
|
+
writeFileReplacing(targetDir, "scripts/proof-cloudflare-version.mjs", createCloudflareVersionProofScript());
|
|
5184
|
+
}
|
|
3562
5185
|
function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
|
|
3563
5186
|
writeJson(targetDir, `${app.directory}/package.json`, createAppPackage(scope, app, packageSource, enableTailwind));
|
|
3564
5187
|
writeJson(targetDir, `${app.directory}/tsconfig.json`, createPackageTsConfig(app.directory, appHasEffectApi(app)));
|
|
3565
|
-
writeFile(targetDir, `${app.directory}/src/modern-app-env.d.ts`,
|
|
5188
|
+
writeFile(targetDir, `${app.directory}/src/modern-app-env.d.ts`, createAppEnvDts(app));
|
|
3566
5189
|
writeFile(targetDir, `${app.directory}/src/ultramodern-build.ts`, createUltramodernBuildModule(scope, app));
|
|
3567
|
-
writeFile(targetDir, `${app.directory}/
|
|
5190
|
+
writeFile(targetDir, `${app.directory}/src/routes/ultramodern-route-metadata.ts`, createRouteMetadataModule(app));
|
|
5191
|
+
writeFile(targetDir, `${app.directory}/modern.config.ts`, createAppModernConfig(scope, app));
|
|
3568
5192
|
writeFile(targetDir, `${app.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(app));
|
|
3569
5193
|
writeJson(targetDir, `${app.directory}/locales/en/translation.json`, createAppLocaleMessages(app, 'en'));
|
|
5194
|
+
writeJson(targetDir, `${app.directory}/locales/en/${appI18nNamespace(app)}.json`, createAppLocaleMessages(app, 'en'));
|
|
3570
5195
|
writeJson(targetDir, `${app.directory}/locales/cs/translation.json`, createAppLocaleMessages(app, 'cs'));
|
|
3571
|
-
|
|
5196
|
+
writeJson(targetDir, `${app.directory}/locales/cs/${appI18nNamespace(app)}.json`, createAppLocaleMessages(app, 'cs'));
|
|
5197
|
+
writeFile(targetDir, `${app.directory}/src/routes/index.css`, createAppStyles(enableTailwind, scope, app));
|
|
3572
5198
|
if (enableTailwind) {
|
|
3573
5199
|
writeFile(targetDir, `${app.directory}/postcss.config.mjs`, createPostcssConfig());
|
|
3574
5200
|
writeFile(targetDir, `${app.directory}/tailwind.config.ts`, createTailwindConfig());
|
|
@@ -3576,6 +5202,7 @@ function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
|
|
|
3576
5202
|
writeFile(targetDir, `${app.directory}/module-federation.config.ts`, 'shell' === app.kind ? createShellModuleFederationConfig() : createRemoteModuleFederationConfig(app));
|
|
3577
5203
|
writeFile(targetDir, `${app.directory}/src/routes/layout.tsx`, createLayout(app.id));
|
|
3578
5204
|
writeFile(targetDir, `${app.directory}/src/routes/[lang]/page.tsx`, 'shell' === app.kind ? createShellPage() : createRemotePage(app));
|
|
5205
|
+
for (const route of createRouteOwnedI18nPaths(app))if ('/' !== route.canonicalPath) writeFile(targetDir, createRoutePageFilePath(app, route.canonicalPath), createRouteAliasPage(route.canonicalPath));
|
|
3579
5206
|
if ('shell' === app.kind) writeFile(targetDir, `${app.directory}/src/effect/recommendations-client.ts`, createShellEffectClient(scope));
|
|
3580
5207
|
if (appHasEffectApi(app)) {
|
|
3581
5208
|
writeFile(targetDir, `${app.directory}/shared/effect/api.ts`, createEffectSharedApi(app));
|
|
@@ -3584,7 +5211,10 @@ function writeApp(targetDir, scope, app, packageSource, enableTailwind) {
|
|
|
3584
5211
|
}
|
|
3585
5212
|
if ('vertical' === app.kind || 'horizontal-remote' === app.kind) {
|
|
3586
5213
|
writeFile(targetDir, `${app.directory}/src/remote-entry.tsx`, createRemoteEntry(app));
|
|
3587
|
-
|
|
5214
|
+
for (const expose of Object.keys(app.exposes ?? {})){
|
|
5215
|
+
const outputPath = remoteComponentOutputPath(app, expose);
|
|
5216
|
+
if (outputPath) writeFile(targetDir, outputPath, createRemoteExposeComponent(app, expose));
|
|
5217
|
+
}
|
|
3588
5218
|
}
|
|
3589
5219
|
if ('horizontal-design-system' === app.kind) {
|
|
3590
5220
|
writeFile(targetDir, `${app.directory}/src/components/button.tsx`, createDesignButton());
|
|
@@ -3601,7 +5231,7 @@ function writeEffectService(targetDir, scope, packageSource, enableTailwind, ser
|
|
|
3601
5231
|
return <main>${service.id} Effect service</main>;
|
|
3602
5232
|
}
|
|
3603
5233
|
`);
|
|
3604
|
-
writeFile(targetDir, `${service.directory}/src/routes/index.css`,
|
|
5234
|
+
writeFile(targetDir, `${service.directory}/src/routes/index.css`, createServiceStyles(enableTailwind, scope, service));
|
|
3605
5235
|
if (enableTailwind) {
|
|
3606
5236
|
writeFile(targetDir, `${service.directory}/postcss.config.mjs`, createPostcssConfig());
|
|
3607
5237
|
writeFile(targetDir, `${service.directory}/tailwind.config.ts`, createTailwindConfig());
|
|
@@ -3619,6 +5249,7 @@ function writeGenericSharedPackage(targetDir, scope, packageSource, sharedPackag
|
|
|
3619
5249
|
});
|
|
3620
5250
|
writeFile(targetDir, `${sharedPackage.directory}/src/index.ts`, `export const packageId = '${sharedPackage.id}';
|
|
3621
5251
|
`);
|
|
5252
|
+
if ('shared-design-tokens' === sharedPackage.id) writeFile(targetDir, `${sharedPackage.directory}/src/tokens.css`, createSharedDesignTokensCss());
|
|
3622
5253
|
}
|
|
3623
5254
|
function writeSharedPackages(targetDir, scope, packageSource) {
|
|
3624
5255
|
for (const sharedPackage of sharedPackages){
|
|
@@ -3644,6 +5275,7 @@ function writeSharedPackages(targetDir, scope, packageSource) {
|
|
|
3644
5275
|
},
|
|
3645
5276
|
} as const;
|
|
3646
5277
|
`);
|
|
5278
|
+
writeFile(targetDir, 'packages/shared-design-tokens/src/tokens.css', createSharedDesignTokensCss());
|
|
3647
5279
|
writeFile(targetDir, 'packages/shared-effect-api/src/index.ts', createEffectSharedApi());
|
|
3648
5280
|
}
|
|
3649
5281
|
function readJsonFile(filePath) {
|
|
@@ -3742,6 +5374,10 @@ function remoteTopologyEntry(scope, remote) {
|
|
|
3742
5374
|
name: remote.mfName,
|
|
3743
5375
|
manifestUrl: `http://localhost:${remote.port}/mf-manifest.json`,
|
|
3744
5376
|
exposes: Object.keys(remote.exposes ?? {}),
|
|
5377
|
+
...remote.remoteRefs?.length ? {
|
|
5378
|
+
remoteRefs: remote.remoteRefs,
|
|
5379
|
+
remotes: createModuleFederationRemoteContracts(remote)
|
|
5380
|
+
} : {},
|
|
3745
5381
|
ssr: true,
|
|
3746
5382
|
fallbackTelemetryEvent: 'modernjs:mv-runtime-parity',
|
|
3747
5383
|
sharedContractVersion: 'mf-ssr-contract-v1'
|
|
@@ -3780,6 +5416,17 @@ function remotesFromTopology(topology, ports) {
|
|
|
3780
5416
|
portEnv: '',
|
|
3781
5417
|
port: 'number' == typeof ports[remote.id] ? ports[remote.id] : 0,
|
|
3782
5418
|
mfName: remote.moduleFederation?.name ?? `remote${toPascalCase(remote.id)}`,
|
|
5419
|
+
...Array.isArray(remote.moduleFederation?.exposes) ? {
|
|
5420
|
+
exposes: Object.fromEntries(remote.moduleFederation.exposes.map((expose)=>[
|
|
5421
|
+
expose,
|
|
5422
|
+
''
|
|
5423
|
+
]))
|
|
5424
|
+
} : {},
|
|
5425
|
+
...Array.isArray(remote.moduleFederation?.remoteRefs) ? {
|
|
5426
|
+
remoteRefs: remote.moduleFederation.remoteRefs
|
|
5427
|
+
} : Array.isArray(remote.moduleFederation?.remotes) ? {
|
|
5428
|
+
remoteRefs: remote.moduleFederation.remotes.map((entry)=>entry.id).filter((id)=>'string' == typeof id)
|
|
5429
|
+
} : {},
|
|
3783
5430
|
...effectApi ? {
|
|
3784
5431
|
effectApi
|
|
3785
5432
|
} : {},
|
|
@@ -3837,7 +5484,10 @@ function addUltramodernMicroVertical(options) {
|
|
|
3837
5484
|
writeJsonFile(ownershipPath, ownership);
|
|
3838
5485
|
writeJsonFile(overlayPath, overlay);
|
|
3839
5486
|
writeJsonFile(node_path.join(options.workspaceRoot, GENERATED_CONTRACT_PATH), createGeneratedContract(scope, [
|
|
3840
|
-
|
|
5487
|
+
{
|
|
5488
|
+
...shellApp,
|
|
5489
|
+
remoteRefs: remotesFromTopology(topology, overlay.ports).map((remote)=>remote.id)
|
|
5490
|
+
},
|
|
3841
5491
|
...remotesFromTopology(topology, overlay.ports)
|
|
3842
5492
|
], enableTailwind));
|
|
3843
5493
|
const shellConfigPath = node_path.join(options.workspaceRoot, `${shellApp.directory}/module-federation.config.ts`);
|
|
@@ -3940,6 +5590,7 @@ function generateUltramodernWorkspace(options) {
|
|
|
3940
5590
|
writeApp(options.targetDir, scope, shellApp, packageSource, enableTailwind);
|
|
3941
5591
|
for (const remote of remoteApps)writeApp(options.targetDir, scope, remote, packageSource, enableTailwind);
|
|
3942
5592
|
writeSharedPackages(options.targetDir, scope, packageSource);
|
|
5593
|
+
writeGeneratedWorkspaceScripts(options.targetDir, scope, enableTailwind);
|
|
3943
5594
|
}
|
|
3944
5595
|
const src_dirname = node_path.dirname(fileURLToPath(import.meta.url));
|
|
3945
5596
|
const templateDir = node_path.resolve(src_dirname, '..', 'template');
|