@bleedingdev/modern-js-create 3.2.0-ultramodern.102 → 3.2.0-ultramodern.104
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 +9 -3
- package/dist/index.js +709 -128
- package/dist/types/locale/index.d.ts +117 -2
- package/dist/types/ultramodern-package-source.d.ts +28 -0
- package/dist/types/ultramodern-workspace.d.ts +1 -2
- package/package.json +3 -3
- package/template/README.md +6 -0
- package/template/config/favicon.svg +5 -0
- package/template/config/public/assets/ultramodern-logo.svg +6 -0
- package/template/config/public/locales/cs/translation.json +5 -0
- package/template/config/public/locales/en/translation.json +5 -0
- package/template/modern.config.ts.handlebars +8 -2
- package/template/scripts/check-i18n-strings.mjs +114 -2
- package/template/scripts/validate-ultramodern.mjs.handlebars +133 -40
- package/template/src/routes/[lang]/page.tsx.handlebars +39 -41
- package/template/src/routes/index.css.handlebars +192 -55
- package/template/tests/ultramodern.contract.test.ts.handlebars +71 -8
- package/template/tsconfig.json +1 -1
- package/template-workspace/README.md.handlebars +12 -3
package/dist/index.js
CHANGED
|
@@ -559,6 +559,64 @@ const localeKeys = i18n.init('en', {
|
|
|
559
559
|
zh: ZH_LOCALE,
|
|
560
560
|
en: EN_LOCALE
|
|
561
561
|
});
|
|
562
|
+
const WORKSPACE_PACKAGE_VERSION = 'workspace:*';
|
|
563
|
+
const BLEEDINGDEV_CREATE_PACKAGE = '@bleedingdev/modern-js-create';
|
|
564
|
+
const BLEEDINGDEV_PACKAGE_SCOPE = 'bleedingdev';
|
|
565
|
+
const BLEEDINGDEV_PACKAGE_NAME_PREFIX = 'modern-js-';
|
|
566
|
+
const BLEEDINGDEV_FRAMEWORK_VERSION_ENV = 'MODERN_CREATE_ULTRAMODERN_FRAMEWORK_VERSION';
|
|
567
|
+
const ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES = [
|
|
568
|
+
'@modern-js/runtime',
|
|
569
|
+
'@modern-js/app-tools',
|
|
570
|
+
'@modern-js/tsconfig',
|
|
571
|
+
'@modern-js/plugin-i18n',
|
|
572
|
+
'@modern-js/plugin-tanstack',
|
|
573
|
+
'@modern-js/plugin-bff',
|
|
574
|
+
'@modern-js/adapter-rstest'
|
|
575
|
+
];
|
|
576
|
+
const ULTRAMODERN_WORKSPACE_MODERN_PACKAGES = [
|
|
577
|
+
'@modern-js/app-tools',
|
|
578
|
+
'@modern-js/plugin-bff',
|
|
579
|
+
'@modern-js/plugin-i18n',
|
|
580
|
+
'@modern-js/plugin-tanstack',
|
|
581
|
+
'@modern-js/runtime'
|
|
582
|
+
];
|
|
583
|
+
function modernPackageVersion(packageSource) {
|
|
584
|
+
return 'install' === packageSource.strategy ? packageSource.modernPackageVersion : WORKSPACE_PACKAGE_VERSION;
|
|
585
|
+
}
|
|
586
|
+
function modernAliasPackageName(packageName, packageSource) {
|
|
587
|
+
if (!packageSource.aliasScope) return packageName;
|
|
588
|
+
const scope = packageSource.aliasScope.replace(/^@/, '');
|
|
589
|
+
const unscopedName = packageName.split('/').at(-1);
|
|
590
|
+
return `@${scope}/${packageSource.aliasPackageNamePrefix ?? ''}${unscopedName}`;
|
|
591
|
+
}
|
|
592
|
+
function modernPackageSpecifier(packageName, packageSource) {
|
|
593
|
+
if ('install' !== packageSource.strategy) return WORKSPACE_PACKAGE_VERSION;
|
|
594
|
+
if (!packageSource.aliasScope) return packageSource.modernPackageVersion;
|
|
595
|
+
return `npm:${modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
|
|
596
|
+
}
|
|
597
|
+
function modernPackageAliases(packageNames, packageSource) {
|
|
598
|
+
if (!packageSource.aliasScope) return;
|
|
599
|
+
return Object.fromEntries(packageNames.map((packageName)=>[
|
|
600
|
+
packageName,
|
|
601
|
+
modernAliasPackageName(packageName, packageSource)
|
|
602
|
+
]));
|
|
603
|
+
}
|
|
604
|
+
function createModernPackagesMetadata(packageNames, packageSource, options = {}) {
|
|
605
|
+
const includeAliases = options.includeAliases ?? Boolean(packageSource.aliasScope);
|
|
606
|
+
const aliases = includeAliases ? modernPackageAliases(packageNames, packageSource) : void 0;
|
|
607
|
+
return {
|
|
608
|
+
packages: [
|
|
609
|
+
...packageNames
|
|
610
|
+
],
|
|
611
|
+
specifier: modernPackageVersion(packageSource),
|
|
612
|
+
...packageSource.registry ? {
|
|
613
|
+
registry: packageSource.registry
|
|
614
|
+
} : {},
|
|
615
|
+
...aliases ? {
|
|
616
|
+
aliases
|
|
617
|
+
} : {}
|
|
618
|
+
};
|
|
619
|
+
}
|
|
562
620
|
const ultramodern_workspace_dirname = node_path.dirname(fileURLToPath(import.meta.url));
|
|
563
621
|
const workspaceTemplateDir = node_path.resolve(ultramodern_workspace_dirname, '..', 'template-workspace');
|
|
564
622
|
const TANSTACK_ROUTER_VERSION = '1.170.11';
|
|
@@ -566,6 +624,7 @@ const MODULE_FEDERATION_VERSION = '2.5.0';
|
|
|
566
624
|
const ZEPHYR_RSPACK_PLUGIN_VERSION = '1.1.1';
|
|
567
625
|
const ZEPHYR_AGENT_VERSION = '1.1.1';
|
|
568
626
|
const WRANGLER_VERSION = '4.95.0';
|
|
627
|
+
const CLOUDFLARE_COMPATIBILITY_DATE = '2026-06-02';
|
|
569
628
|
const TAILWIND_VERSION = '4.3.0';
|
|
570
629
|
const TAILWIND_POSTCSS_VERSION = '4.3.0';
|
|
571
630
|
const EFFECT_TSGO_VERSION = '0.13.0';
|
|
@@ -580,7 +639,6 @@ const REACT_VERSION = '^19.2.6';
|
|
|
580
639
|
const REACT_DOM_VERSION = '^19.2.6';
|
|
581
640
|
const REACT_ROUTER_DOM_VERSION = '7.16.0';
|
|
582
641
|
const PNPM_VERSION = '11.5.0';
|
|
583
|
-
const WORKSPACE_PACKAGE_VERSION = 'workspace:*';
|
|
584
642
|
const GENERATED_CONTRACT_PATH = '.modernjs/ultramodern-generated-contract.json';
|
|
585
643
|
const RSTACK_AGENT_SKILLS_COMMIT = '61c948b42512e223bad44b83af4080eba48b2677';
|
|
586
644
|
const MODULE_FEDERATION_AGENT_SKILLS_COMMIT = '07bb5b6c43ad457609e00c081b72d4c42508ec76';
|
|
@@ -604,13 +662,6 @@ const privateAgentSkills = [
|
|
|
604
662
|
'debugger-mode'
|
|
605
663
|
];
|
|
606
664
|
const effectTsgoTypecheckCommand = "node -e \"const fs = require('node:fs'); const { execFileSync, spawnSync } = require('node:child_process'); const bin = execFileSync('effect-tsgo', ['get-exe-path'], { encoding: 'utf8' }).trim(); if (process.platform !== 'win32') fs.chmodSync(bin, 0o755); const result = spawnSync(bin, ['--noEmit', '-p', 'tsconfig.json'], { stdio: 'inherit' }); process.exit(result.status ?? 1);\"";
|
|
607
|
-
const modernPackageNames = [
|
|
608
|
-
'@modern-js/app-tools',
|
|
609
|
-
'@modern-js/plugin-bff',
|
|
610
|
-
'@modern-js/plugin-i18n',
|
|
611
|
-
'@modern-js/plugin-tanstack',
|
|
612
|
-
'@modern-js/runtime'
|
|
613
|
-
];
|
|
614
665
|
function sortJsonValue(value) {
|
|
615
666
|
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
616
667
|
if (null !== value && 'object' == typeof value) return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, entry])=>[
|
|
@@ -619,6 +670,10 @@ function sortJsonValue(value) {
|
|
|
619
670
|
]));
|
|
620
671
|
return value;
|
|
621
672
|
}
|
|
673
|
+
const supportedWorkspaceLanguages = [
|
|
674
|
+
'en',
|
|
675
|
+
'cs'
|
|
676
|
+
];
|
|
622
677
|
const ULTRAMODERN_WORKSPACE_FLAG = '--ultramodern-workspace';
|
|
623
678
|
const FIRST_VERTICAL_PORT = 4101;
|
|
624
679
|
const TAILWIND_PREFIX_DIGIT_WORDS = [
|
|
@@ -913,28 +968,23 @@ function relativeRootFor(packageDir) {
|
|
|
913
968
|
return normalizePath(node_path.relative(packageDir, '.') || '.');
|
|
914
969
|
}
|
|
915
970
|
function resolvePackageSource(options) {
|
|
916
|
-
const strategy = options.packageSource?.strategy ?? '
|
|
917
|
-
return {
|
|
971
|
+
const strategy = options.packageSource?.strategy ?? 'install';
|
|
972
|
+
if ('workspace' === strategy) return {
|
|
918
973
|
strategy,
|
|
919
|
-
modernPackageVersion:
|
|
974
|
+
modernPackageVersion: WORKSPACE_PACKAGE_VERSION,
|
|
920
975
|
registry: options.packageSource?.registry,
|
|
921
976
|
aliasScope: options.packageSource?.aliasScope,
|
|
922
977
|
aliasPackageNamePrefix: options.packageSource?.aliasPackageNamePrefix
|
|
923
978
|
};
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
return
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
}
|
|
934
|
-
function modernPackageSpecifier(packageName, packageSource) {
|
|
935
|
-
if ('install' !== packageSource.strategy) return WORKSPACE_PACKAGE_VERSION;
|
|
936
|
-
if (!packageSource.aliasScope) return packageSource.modernPackageVersion;
|
|
937
|
-
return `npm:${modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
|
|
979
|
+
const registry = options.packageSource?.registry;
|
|
980
|
+
const aliasScope = options.packageSource?.aliasScope ?? (registry ? void 0 : BLEEDINGDEV_PACKAGE_SCOPE);
|
|
981
|
+
return {
|
|
982
|
+
strategy,
|
|
983
|
+
modernPackageVersion: options.packageSource?.modernPackageVersion ?? options.modernVersion,
|
|
984
|
+
registry,
|
|
985
|
+
aliasScope,
|
|
986
|
+
aliasPackageNamePrefix: options.packageSource?.aliasPackageNamePrefix ?? (aliasScope ? BLEEDINGDEV_PACKAGE_NAME_PREFIX : void 0)
|
|
987
|
+
};
|
|
938
988
|
}
|
|
939
989
|
function appDependencies(scope, packageSource, app, remotes = []) {
|
|
940
990
|
const dependencies = {
|
|
@@ -1101,17 +1151,105 @@ function createCloudflareProofRoute(app) {
|
|
|
1101
1151
|
} : {}
|
|
1102
1152
|
};
|
|
1103
1153
|
}
|
|
1154
|
+
function createCloudflareSecurityContract() {
|
|
1155
|
+
return {
|
|
1156
|
+
enabled: true,
|
|
1157
|
+
headers: {
|
|
1158
|
+
referrerPolicy: 'strict-origin-when-cross-origin',
|
|
1159
|
+
contentTypeOptions: 'nosniff',
|
|
1160
|
+
permissionsPolicy: 'camera=(), geolocation=(), microphone=(), payment=(), usb=()'
|
|
1161
|
+
},
|
|
1162
|
+
contentSecurityPolicy: {
|
|
1163
|
+
mode: 'report-only',
|
|
1164
|
+
directives: {
|
|
1165
|
+
'base-uri': [
|
|
1166
|
+
"'self'"
|
|
1167
|
+
],
|
|
1168
|
+
'connect-src': [
|
|
1169
|
+
"'self'",
|
|
1170
|
+
'https:',
|
|
1171
|
+
'http:',
|
|
1172
|
+
'wss:',
|
|
1173
|
+
'ws:'
|
|
1174
|
+
],
|
|
1175
|
+
'default-src': [
|
|
1176
|
+
"'self'"
|
|
1177
|
+
],
|
|
1178
|
+
'font-src': [
|
|
1179
|
+
"'self'",
|
|
1180
|
+
'data:',
|
|
1181
|
+
'https:',
|
|
1182
|
+
'http:'
|
|
1183
|
+
],
|
|
1184
|
+
'form-action': [
|
|
1185
|
+
"'self'"
|
|
1186
|
+
],
|
|
1187
|
+
'frame-ancestors': [
|
|
1188
|
+
"'self'"
|
|
1189
|
+
],
|
|
1190
|
+
'img-src': [
|
|
1191
|
+
"'self'",
|
|
1192
|
+
'data:',
|
|
1193
|
+
'blob:',
|
|
1194
|
+
'https:',
|
|
1195
|
+
'http:'
|
|
1196
|
+
],
|
|
1197
|
+
'manifest-src': [
|
|
1198
|
+
"'self'",
|
|
1199
|
+
'https:',
|
|
1200
|
+
'http:'
|
|
1201
|
+
],
|
|
1202
|
+
'object-src': [
|
|
1203
|
+
"'none'"
|
|
1204
|
+
],
|
|
1205
|
+
"script-src": [
|
|
1206
|
+
"'self'",
|
|
1207
|
+
"'unsafe-inline'",
|
|
1208
|
+
"'unsafe-eval'",
|
|
1209
|
+
'https:',
|
|
1210
|
+
'http:',
|
|
1211
|
+
'blob:'
|
|
1212
|
+
],
|
|
1213
|
+
'style-src': [
|
|
1214
|
+
"'self'",
|
|
1215
|
+
"'unsafe-inline'",
|
|
1216
|
+
'https:',
|
|
1217
|
+
'http:'
|
|
1218
|
+
],
|
|
1219
|
+
'worker-src': [
|
|
1220
|
+
"'self'",
|
|
1221
|
+
'blob:'
|
|
1222
|
+
]
|
|
1223
|
+
},
|
|
1224
|
+
reason: "Report-only by default so Cloudflare Module Federation SSR can prove remote script, style, and connect compatibility before enforcement."
|
|
1225
|
+
},
|
|
1226
|
+
noindex: {
|
|
1227
|
+
workersDev: true,
|
|
1228
|
+
localhost: true,
|
|
1229
|
+
previewHostnames: []
|
|
1230
|
+
},
|
|
1231
|
+
cookies: {
|
|
1232
|
+
mutateSetCookie: false,
|
|
1233
|
+
reason: 'Generated Cloudflare worker does not own application Set-Cookie headers.'
|
|
1234
|
+
}
|
|
1235
|
+
};
|
|
1236
|
+
}
|
|
1237
|
+
function formatTsJsonValue(value, indent) {
|
|
1238
|
+
return JSON.stringify(value, null, 2).replaceAll('\n', `\n${' '.repeat(indent)}`);
|
|
1239
|
+
}
|
|
1104
1240
|
function createCloudflareDeployContract(scope, app) {
|
|
1105
1241
|
return {
|
|
1106
1242
|
target: 'cloudflare',
|
|
1107
1243
|
workerName: createCloudflareWorkerName(scope, app),
|
|
1108
1244
|
publicUrlEnv: createCloudflarePublicUrlEnv(app),
|
|
1245
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE,
|
|
1109
1246
|
compatibilityFlags: [
|
|
1110
1247
|
'nodejs_compat',
|
|
1111
1248
|
'global_fetch_strictly_public'
|
|
1112
1249
|
],
|
|
1113
1250
|
assetsBinding: 'ASSETS',
|
|
1114
1251
|
routes: createCloudflareProofRoute(app),
|
|
1252
|
+
security: createCloudflareSecurityContract(),
|
|
1115
1253
|
evidence: {
|
|
1116
1254
|
proofScript: "scripts/proof-cloudflare-version.mjs",
|
|
1117
1255
|
reportDefault: '.codex/reports/cloudflare-version-proof/public-url-proof.json'
|
|
@@ -1331,7 +1469,19 @@ if (
|
|
|
1331
1469
|
export default defineConfig(
|
|
1332
1470
|
presetUltramodern(
|
|
1333
1471
|
{
|
|
1334
|
-
${bffConfig}
|
|
1472
|
+
${bffConfig} ...(cloudflareDeployEnabled
|
|
1473
|
+
? {
|
|
1474
|
+
deploy: {
|
|
1475
|
+
worker: {
|
|
1476
|
+
compatibilityDate: '${CLOUDFLARE_COMPATIBILITY_DATE}',
|
|
1477
|
+
name: cloudflareWorkerName,
|
|
1478
|
+
security: ${formatTsJsonValue(sortJsonValue(createCloudflareSecurityContract()), 16)},
|
|
1479
|
+
ssr: true,
|
|
1480
|
+
},
|
|
1481
|
+
},
|
|
1482
|
+
}
|
|
1483
|
+
: {}),
|
|
1484
|
+
html: {
|
|
1335
1485
|
outputStructure: 'flat',
|
|
1336
1486
|
},
|
|
1337
1487
|
output: {
|
|
@@ -1368,6 +1518,9 @@ ${bffConfig} html: {
|
|
|
1368
1518
|
'/mf-manifest.json',
|
|
1369
1519
|
'/mf-stats.json',
|
|
1370
1520
|
'/remoteEntry.js',
|
|
1521
|
+
'/robots.txt',
|
|
1522
|
+
'/site.webmanifest',
|
|
1523
|
+
'/sitemap.xml',
|
|
1371
1524
|
'/static',
|
|
1372
1525
|
'/zephyr-manifest.json',
|
|
1373
1526
|
],
|
|
@@ -1380,6 +1533,20 @@ ${bffConfig} html: {
|
|
|
1380
1533
|
${bffPluginEntry} moduleFederationPlugin(),
|
|
1381
1534
|
zephyrRspackPlugin(),
|
|
1382
1535
|
],
|
|
1536
|
+
server: {
|
|
1537
|
+
port,
|
|
1538
|
+
publicDir: ['./locales', './assets'],
|
|
1539
|
+
ssr: {
|
|
1540
|
+
mode: 'string',
|
|
1541
|
+
moduleFederationAppSSR: true,
|
|
1542
|
+
},
|
|
1543
|
+
},
|
|
1544
|
+
source: {
|
|
1545
|
+
globalVars: {
|
|
1546
|
+
ULTRAMODERN_SITE_URL: siteUrl,
|
|
1547
|
+
},
|
|
1548
|
+
mainEntryName: 'index',
|
|
1549
|
+
},
|
|
1383
1550
|
tools: {
|
|
1384
1551
|
autoprefixer: {
|
|
1385
1552
|
overrideBrowserslist: ['defaults'],
|
|
@@ -1396,29 +1563,6 @@ ${bffPluginEntry} moduleFederationPlugin(),
|
|
|
1396
1563
|
]);
|
|
1397
1564
|
},
|
|
1398
1565
|
},
|
|
1399
|
-
...(cloudflareDeployEnabled
|
|
1400
|
-
? {
|
|
1401
|
-
deploy: {
|
|
1402
|
-
worker: {
|
|
1403
|
-
ssr: true,
|
|
1404
|
-
},
|
|
1405
|
-
},
|
|
1406
|
-
}
|
|
1407
|
-
: {}),
|
|
1408
|
-
server: {
|
|
1409
|
-
port,
|
|
1410
|
-
publicDir: ['./locales', './assets'],
|
|
1411
|
-
ssr: {
|
|
1412
|
-
mode: 'string',
|
|
1413
|
-
moduleFederationAppSSR: true,
|
|
1414
|
-
},
|
|
1415
|
-
},
|
|
1416
|
-
source: {
|
|
1417
|
-
globalVars: {
|
|
1418
|
-
ULTRAMODERN_SITE_URL: siteUrl,
|
|
1419
|
-
},
|
|
1420
|
-
mainEntryName: 'index',
|
|
1421
|
-
},
|
|
1422
1566
|
},
|
|
1423
1567
|
{
|
|
1424
1568
|
appId,
|
|
@@ -1638,12 +1782,18 @@ ${createModuleFederationRemotesConfig(scope, app, remotes)}${createSharedModuleF
|
|
|
1638
1782
|
function appI18nNamespace(app) {
|
|
1639
1783
|
return 'shell' === app.kind ? 'shell' : app.domain ?? app.id;
|
|
1640
1784
|
}
|
|
1785
|
+
const privateAppRoutePublicness = {
|
|
1786
|
+
indexable: false,
|
|
1787
|
+
public: false,
|
|
1788
|
+
publicSurface: 'private-app-screen'
|
|
1789
|
+
};
|
|
1641
1790
|
function createRouteOwnedI18nPaths(app) {
|
|
1642
1791
|
const namespace = appI18nNamespace(app);
|
|
1643
1792
|
const base = {
|
|
1644
1793
|
mfBoundaryId: app.mfName,
|
|
1645
1794
|
namespace,
|
|
1646
|
-
ownerAppId: app.id
|
|
1795
|
+
ownerAppId: app.id,
|
|
1796
|
+
...privateAppRoutePublicness
|
|
1647
1797
|
};
|
|
1648
1798
|
if ('shell' === app.kind) return [
|
|
1649
1799
|
{
|
|
@@ -1816,8 +1966,11 @@ function createRouteOwnedI18nPaths(app) {
|
|
|
1816
1966
|
}
|
|
1817
1967
|
];
|
|
1818
1968
|
}
|
|
1819
|
-
function
|
|
1820
|
-
return
|
|
1969
|
+
function isPublicIndexableRoute(route) {
|
|
1970
|
+
return route.public && route.indexable;
|
|
1971
|
+
}
|
|
1972
|
+
function createLocalisedUrlsMapFromRoutes(routes) {
|
|
1973
|
+
return Object.fromEntries(routes.flatMap((route)=>{
|
|
1821
1974
|
if ('/' === route.canonicalPath) return [];
|
|
1822
1975
|
return Array.from(new Set([
|
|
1823
1976
|
route.canonicalPath,
|
|
@@ -1828,9 +1981,23 @@ function createLocalisedUrlsMap(app) {
|
|
|
1828
1981
|
]);
|
|
1829
1982
|
}));
|
|
1830
1983
|
}
|
|
1984
|
+
function createLocalisedUrlsMap(app) {
|
|
1985
|
+
return createLocalisedUrlsMapFromRoutes(createRouteOwnedI18nPaths(app));
|
|
1986
|
+
}
|
|
1987
|
+
function createPublicRouteMetadata(app) {
|
|
1988
|
+
return createRouteOwnedI18nPaths(app).filter(isPublicIndexableRoute).map((route)=>({
|
|
1989
|
+
canonicalPath: route.canonicalPath,
|
|
1990
|
+
id: route.id,
|
|
1991
|
+
localisedPaths: route.localisedPaths,
|
|
1992
|
+
namespace: route.namespace,
|
|
1993
|
+
ownerAppId: route.ownerAppId,
|
|
1994
|
+
titleKey: route.titleKey
|
|
1995
|
+
}));
|
|
1996
|
+
}
|
|
1831
1997
|
function createRouteMetadataModule(app) {
|
|
1832
1998
|
const routes = sortJsonValue(createRouteOwnedI18nPaths(app));
|
|
1833
1999
|
const localisedUrls = sortJsonValue(createLocalisedUrlsMap(app));
|
|
2000
|
+
const publicRoutes = sortJsonValue(createPublicRouteMetadata(app));
|
|
1834
2001
|
const namespace = appI18nNamespace(app);
|
|
1835
2002
|
return `export const ultramodernRouteNamespace = '${namespace}' as const;
|
|
1836
2003
|
|
|
@@ -1838,9 +2005,12 @@ export const ultramodernRouteMetadata = ${JSON.stringify(routes, null, 2)} as co
|
|
|
1838
2005
|
|
|
1839
2006
|
export const ultramodernLocalisedUrls = ${JSON.stringify(localisedUrls, null, 2)} as const;
|
|
1840
2007
|
|
|
2008
|
+
export const ultramodernPublicRoutes = ${JSON.stringify(publicRoutes, null, 2)} as const;
|
|
2009
|
+
|
|
1841
2010
|
export const ultramodernRouteConfig = {
|
|
1842
2011
|
localisedUrls: ultramodernLocalisedUrls,
|
|
1843
2012
|
namespace: ultramodernRouteNamespace,
|
|
2013
|
+
publicRoutes: ultramodernPublicRoutes,
|
|
1844
2014
|
routes: ultramodernRouteMetadata,
|
|
1845
2015
|
source: 'route-owned',
|
|
1846
2016
|
} as const;
|
|
@@ -2011,15 +2181,125 @@ function createPostcssConfig() {
|
|
|
2011
2181
|
function createTailwindConfig() {
|
|
2012
2182
|
return `import type { Config } from 'tailwindcss';
|
|
2013
2183
|
|
|
2014
|
-
export default {
|
|
2015
|
-
} satisfies Config;
|
|
2184
|
+
export default {} satisfies Config;
|
|
2016
2185
|
`;
|
|
2017
2186
|
}
|
|
2018
2187
|
function createTw(prefix) {
|
|
2019
2188
|
return (classList)=>classList.split(/\s+/u).filter(Boolean).map((candidate)=>`${prefix}:${candidate.replace(/\[&&\]:/gu, '')}`).join(' ');
|
|
2020
2189
|
}
|
|
2021
|
-
|
|
2022
|
-
|
|
2190
|
+
const publicSurfaceRequiredAssetPaths = [
|
|
2191
|
+
'config/public/robots.txt'
|
|
2192
|
+
];
|
|
2193
|
+
const publicSurfaceOptionalAssetPaths = [
|
|
2194
|
+
'config/public/sitemap.xml',
|
|
2195
|
+
'config/public/site.webmanifest'
|
|
2196
|
+
];
|
|
2197
|
+
function normalisePublicPath(pathname) {
|
|
2198
|
+
const normalised = pathname.trim().replaceAll(/\/+/gu, '/').replace(/\/+$/u, '');
|
|
2199
|
+
return normalised.length > 0 && normalised.startsWith('/') ? normalised : `/${normalised}`;
|
|
2200
|
+
}
|
|
2201
|
+
function createLocalisedPublicPath(pathname, language) {
|
|
2202
|
+
const publicPath = normalisePublicPath(pathname);
|
|
2203
|
+
return '/' === publicPath ? `/${language}` : `/${language}${publicPath}`;
|
|
2204
|
+
}
|
|
2205
|
+
function isConcretePublicPath(pathname) {
|
|
2206
|
+
return !normalisePublicPath(pathname).split('/').some((segment)=>segment.startsWith(':') || segment.includes('*') || segment.startsWith('['));
|
|
2207
|
+
}
|
|
2208
|
+
function uniqueSorted(values) {
|
|
2209
|
+
return Array.from(new Set(values)).sort((left, right)=>left.localeCompare(right));
|
|
2210
|
+
}
|
|
2211
|
+
function createPublicSurfaceRouteEntries(app) {
|
|
2212
|
+
return createPublicRouteMetadata(app).map((route)=>{
|
|
2213
|
+
const localeUrlPaths = Object.fromEntries(supportedWorkspaceLanguages.map((language)=>[
|
|
2214
|
+
language,
|
|
2215
|
+
createLocalisedPublicPath(route.localisedPaths[language], language)
|
|
2216
|
+
]));
|
|
2217
|
+
if (!Object.values(localeUrlPaths).every(isConcretePublicPath)) return;
|
|
2218
|
+
return {
|
|
2219
|
+
...route,
|
|
2220
|
+
canonicalUrlPath: localeUrlPaths.en,
|
|
2221
|
+
localeUrlPaths
|
|
2222
|
+
};
|
|
2223
|
+
}).filter((route)=>void 0 !== route).sort((left, right)=>left.canonicalUrlPath.localeCompare(right.canonicalUrlPath) || left.id.localeCompare(right.id));
|
|
2224
|
+
}
|
|
2225
|
+
function createPublicSurfaceUrlPaths(app) {
|
|
2226
|
+
return uniqueSorted(createPublicSurfaceRouteEntries(app).flatMap((route)=>supportedWorkspaceLanguages.map((language)=>route.localeUrlPaths[language])));
|
|
2227
|
+
}
|
|
2228
|
+
function createPublicSurfaceOrigin(app) {
|
|
2229
|
+
return `http://localhost:${app.port}`;
|
|
2230
|
+
}
|
|
2231
|
+
function escapeXmlText(value) {
|
|
2232
|
+
return value.replaceAll('&', '&').replaceAll('<', '<').replaceAll('>', '>');
|
|
2233
|
+
}
|
|
2234
|
+
function escapeXmlAttribute(value) {
|
|
2235
|
+
return escapeXmlText(value).replaceAll('"', '"');
|
|
2236
|
+
}
|
|
2237
|
+
function renderRobotsTxt(app) {
|
|
2238
|
+
const urlPaths = createPublicSurfaceUrlPaths(app);
|
|
2239
|
+
const lines = [
|
|
2240
|
+
'User-agent: *'
|
|
2241
|
+
];
|
|
2242
|
+
if (0 === urlPaths.length) lines.push('Disallow: /');
|
|
2243
|
+
else {
|
|
2244
|
+
for (const urlPath of urlPaths)lines.push(`Allow: ${urlPath}$`);
|
|
2245
|
+
lines.push('Disallow: /');
|
|
2246
|
+
lines.push(`Sitemap: ${createPublicSurfaceOrigin(app)}/sitemap.xml`);
|
|
2247
|
+
}
|
|
2248
|
+
return `${lines.join('\n')}\n`;
|
|
2249
|
+
}
|
|
2250
|
+
function renderSitemapXml(app) {
|
|
2251
|
+
const origin = createPublicSurfaceOrigin(app);
|
|
2252
|
+
const routes = createPublicSurfaceRouteEntries(app);
|
|
2253
|
+
const lines = [
|
|
2254
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
2255
|
+
'<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9" xmlns:xhtml="http://www.w3.org/1999/xhtml">'
|
|
2256
|
+
];
|
|
2257
|
+
for (const route of routes)for (const language of supportedWorkspaceLanguages){
|
|
2258
|
+
lines.push(' <url>');
|
|
2259
|
+
lines.push(` <loc>${escapeXmlText(`${origin}${route.localeUrlPaths[language]}`)}</loc>`);
|
|
2260
|
+
for (const alternateLanguage of supportedWorkspaceLanguages)lines.push(` <xhtml:link rel="alternate" hreflang="${alternateLanguage}" href="${escapeXmlAttribute(`${origin}${route.localeUrlPaths[alternateLanguage]}`)}" />`);
|
|
2261
|
+
lines.push(` <xhtml:link rel="alternate" hreflang="x-default" href="${escapeXmlAttribute(`${origin}${route.localeUrlPaths.en}`)}" />`);
|
|
2262
|
+
lines.push(' </url>');
|
|
2263
|
+
}
|
|
2264
|
+
lines.push('</urlset>');
|
|
2265
|
+
return `${lines.join('\n')}\n`;
|
|
2266
|
+
}
|
|
2267
|
+
function renderWebManifest(app) {
|
|
2268
|
+
const startUrl = createPublicSurfaceUrlPaths(app)[0];
|
|
2269
|
+
const manifest = {
|
|
2270
|
+
name: app.displayName,
|
|
2271
|
+
short_name: app.displayName,
|
|
2272
|
+
display: 'standalone',
|
|
2273
|
+
background_color: '#ffffff',
|
|
2274
|
+
theme_color: '#133225',
|
|
2275
|
+
lang: 'en',
|
|
2276
|
+
categories: [
|
|
2277
|
+
'business',
|
|
2278
|
+
'productivity'
|
|
2279
|
+
],
|
|
2280
|
+
icons: [],
|
|
2281
|
+
...startUrl ? {
|
|
2282
|
+
scope: '/',
|
|
2283
|
+
start_url: startUrl
|
|
2284
|
+
} : {}
|
|
2285
|
+
};
|
|
2286
|
+
return `${JSON.stringify(sortJsonValue(manifest), null, 2)}\n`;
|
|
2287
|
+
}
|
|
2288
|
+
function createPublicSurfaceAssets(app) {
|
|
2289
|
+
const assets = {
|
|
2290
|
+
'config/public/robots.txt': renderRobotsTxt(app)
|
|
2291
|
+
};
|
|
2292
|
+
if (createPublicSurfaceRouteEntries(app).length > 0) {
|
|
2293
|
+
assets['config/public/sitemap.xml'] = renderSitemapXml(app);
|
|
2294
|
+
assets['config/public/site.webmanifest'] = renderWebManifest(app);
|
|
2295
|
+
}
|
|
2296
|
+
return assets;
|
|
2297
|
+
}
|
|
2298
|
+
function workspaceAssetsForApp(app) {
|
|
2299
|
+
return createPublicSurfaceAssets(app);
|
|
2300
|
+
}
|
|
2301
|
+
function rewriteWorkspaceAssetsForApp(workspaceRoot, app) {
|
|
2302
|
+
for (const [relativePath, content] of Object.entries(workspaceAssetsForApp(app)))writeFileReplacing(workspaceRoot, `${app.directory}/${relativePath}`, content);
|
|
2023
2303
|
}
|
|
2024
2304
|
function createLocalizedHeadComponent() {
|
|
2025
2305
|
return `const fallbackLanguage = 'en';
|
|
@@ -3969,19 +4249,10 @@ function createDevelopmentOverlay(remotes = []) {
|
|
|
3969
4249
|
};
|
|
3970
4250
|
}
|
|
3971
4251
|
function createPackageSourceMetadata(scope, packageSource) {
|
|
3972
|
-
const modernPackages = {
|
|
3973
|
-
packages: modernPackageNames,
|
|
3974
|
-
specifier: modernPackageVersion(packageSource)
|
|
3975
|
-
};
|
|
3976
|
-
if (packageSource.registry) modernPackages.registry = packageSource.registry;
|
|
3977
|
-
if (packageSource.aliasScope) modernPackages.aliases = Object.fromEntries(modernPackageNames.map((packageName)=>[
|
|
3978
|
-
packageName,
|
|
3979
|
-
modernAliasPackageName(packageName, packageSource)
|
|
3980
|
-
]));
|
|
3981
4252
|
return {
|
|
3982
4253
|
schemaVersion: 1,
|
|
3983
4254
|
strategy: packageSource.strategy,
|
|
3984
|
-
modernPackages,
|
|
4255
|
+
modernPackages: createModernPackagesMetadata(ULTRAMODERN_WORKSPACE_MODERN_PACKAGES, packageSource),
|
|
3985
4256
|
generatedWorkspacePackages: {
|
|
3986
4257
|
packages: sharedPackages.map((sharedPackage)=>ultramodern_workspace_packageName(scope, sharedPackage.id)),
|
|
3987
4258
|
specifier: WORKSPACE_PACKAGE_VERSION
|
|
@@ -4231,6 +4502,23 @@ function createStylingContract(scope, app, enableTailwind) {
|
|
|
4231
4502
|
federation: createAppCssFederationContract(scope, app)
|
|
4232
4503
|
};
|
|
4233
4504
|
}
|
|
4505
|
+
function createPublicSurfaceContract(app) {
|
|
4506
|
+
const files = Object.keys(createPublicSurfaceAssets(app)).sort().map((relativePath)=>relativePath.replace(/^config\/public\//u, ''));
|
|
4507
|
+
return {
|
|
4508
|
+
source: 'route-owned-public-routes',
|
|
4509
|
+
metadataExport: './src/routes/ultramodern-route-metadata',
|
|
4510
|
+
staticRoot: 'config/public',
|
|
4511
|
+
privateRoutePolicy: 'omit-from-generated-public-surface',
|
|
4512
|
+
files,
|
|
4513
|
+
omittedByDefault: [
|
|
4514
|
+
'api-catalog.json',
|
|
4515
|
+
'llms.txt',
|
|
4516
|
+
'security.txt'
|
|
4517
|
+
],
|
|
4518
|
+
publicRoutes: createPublicRouteMetadata(app),
|
|
4519
|
+
concreteUrlPaths: createPublicSurfaceUrlPaths(app)
|
|
4520
|
+
};
|
|
4521
|
+
}
|
|
4234
4522
|
function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
4235
4523
|
const appWithResolvedRefs = 'shell' === app.kind ? {
|
|
4236
4524
|
...app,
|
|
@@ -4248,7 +4536,9 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
|
4248
4536
|
target: 'cloudflare',
|
|
4249
4537
|
cloudflare: createCloudflareDeployContract(scope, app),
|
|
4250
4538
|
worker: {
|
|
4539
|
+
compatibilityDate: CLOUDFLARE_COMPATIBILITY_DATE,
|
|
4251
4540
|
name: createCloudflareWorkerName(scope, app),
|
|
4541
|
+
security: createCloudflareSecurityContract(),
|
|
4252
4542
|
ssr: true
|
|
4253
4543
|
},
|
|
4254
4544
|
output: {
|
|
@@ -4293,8 +4583,12 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
|
4293
4583
|
metadataExport: './src/routes/ultramodern-route-metadata',
|
|
4294
4584
|
localisedUrls: createLocalisedUrlsMap(app),
|
|
4295
4585
|
owned: createRouteOwnedI18nPaths(app),
|
|
4586
|
+
publicRoutes: createPublicRouteMetadata(app),
|
|
4587
|
+
privateByDefault: true,
|
|
4588
|
+
publicnessDefault: 'private-app-screen',
|
|
4296
4589
|
generatedRouteMap: true,
|
|
4297
|
-
manualOverrides: []
|
|
4590
|
+
manualOverrides: [],
|
|
4591
|
+
publicSurface: createPublicSurfaceContract(app)
|
|
4298
4592
|
},
|
|
4299
4593
|
moduleFederation: {
|
|
4300
4594
|
name: app.mfName,
|
|
@@ -4770,6 +5064,7 @@ function createWorkspaceValidationScript(scope, enableTailwind, remotes = []) {
|
|
|
4770
5064
|
const expectedBuildScript = remotes.length > 0 ? 'ULTRAMODERN_ZEPHYR=false pnpm -r --filter "./verticals/*" run build && ULTRAMODERN_ZEPHYR=false pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types' : 'ULTRAMODERN_ZEPHYR=false pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types';
|
|
4771
5065
|
const expectedCloudflareBuildScript = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run cloudflare:build && pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types' : 'pnpm --filter "./apps/shell-super-app" run cloudflare:build && pnpm ultramodern:assert-mf-types';
|
|
4772
5066
|
const expectedCloudflareDeployScript = remotes.length > 0 ? 'pnpm -r --filter "./verticals/*" run cloudflare:deploy && pnpm --filter "./apps/shell-super-app" run cloudflare:deploy' : 'pnpm --filter "./apps/shell-super-app" run cloudflare:deploy';
|
|
5067
|
+
const expectedCloudflareSecurity = createCloudflareSecurityContract();
|
|
4773
5068
|
return `import { execFileSync } from 'node:child_process';
|
|
4774
5069
|
import fs from 'node:fs';
|
|
4775
5070
|
import path from 'node:path';
|
|
@@ -4784,6 +5079,22 @@ const oldRemotePaths = ${JSON.stringify(oldRemotePaths, null, 2)};
|
|
|
4784
5079
|
const expectedBuildScript = ${JSON.stringify(expectedBuildScript)};
|
|
4785
5080
|
const expectedCloudflareBuildScript = ${JSON.stringify(expectedCloudflareBuildScript)};
|
|
4786
5081
|
const expectedCloudflareDeployScript = ${JSON.stringify(expectedCloudflareDeployScript)};
|
|
5082
|
+
const expectedCloudflareSecurity = ${JSON.stringify(expectedCloudflareSecurity, null, 2)};
|
|
5083
|
+
const publicSurfaceRequiredAssetPaths = ${JSON.stringify([
|
|
5084
|
+
...publicSurfaceRequiredAssetPaths
|
|
5085
|
+
], null, 2)};
|
|
5086
|
+
const publicSurfaceOptionalAssetPaths = ${JSON.stringify([
|
|
5087
|
+
...publicSurfaceOptionalAssetPaths
|
|
5088
|
+
], null, 2)};
|
|
5089
|
+
const expectedModernPackageSpecifier = packageName => {
|
|
5090
|
+
if (packageSource.strategy === 'workspace') {
|
|
5091
|
+
return 'workspace:*';
|
|
5092
|
+
}
|
|
5093
|
+
const aliases = packageSource.modernPackages?.aliases ?? {};
|
|
5094
|
+
const alias = aliases[packageName];
|
|
5095
|
+
const specifier = packageSource.modernPackages?.specifier;
|
|
5096
|
+
return typeof alias === 'string' ? \`npm:\${alias}@\${specifier}\` : specifier;
|
|
5097
|
+
};
|
|
4787
5098
|
|
|
4788
5099
|
const readText = relativePath => fs.readFileSync(path.join(root, relativePath), 'utf-8');
|
|
4789
5100
|
const readJson = relativePath => JSON.parse(readText(relativePath));
|
|
@@ -4798,6 +5109,21 @@ const assertExists = relativePath => {
|
|
|
4798
5109
|
const assertNotExists = relativePath => {
|
|
4799
5110
|
assert(!fs.existsSync(path.join(root, relativePath)), \`Unexpected \${relativePath}\`);
|
|
4800
5111
|
};
|
|
5112
|
+
const assertPublicSurfaceAssets = (appPath, publicRoutes) => {
|
|
5113
|
+
const robots = readText(\`\${appPath}/config/public/robots.txt\`);
|
|
5114
|
+
if ((publicRoutes ?? []).length === 0) {
|
|
5115
|
+
assert(robots.includes('Disallow: /'), \`\${appPath} robots.txt must disallow crawling when no public routes exist\`);
|
|
5116
|
+
for (const relativePath of publicSurfaceOptionalAssetPaths) {
|
|
5117
|
+
assertNotExists(\`\${appPath}/\${relativePath}\`);
|
|
5118
|
+
}
|
|
5119
|
+
return;
|
|
5120
|
+
}
|
|
5121
|
+
const sitemap = readText(\`\${appPath}/config/public/sitemap.xml\`);
|
|
5122
|
+
const manifest = readJson(\`\${appPath}/config/public/site.webmanifest\`);
|
|
5123
|
+
assert(!sitemap.includes('<lastmod>'), \`\${appPath} sitemap must omit build-time lastmod values\`);
|
|
5124
|
+
assert(typeof manifest.name === 'string' && manifest.name.length > 0, \`\${appPath} web manifest must include a safe app name\`);
|
|
5125
|
+
assert(typeof manifest.start_url === 'string' && manifest.start_url.startsWith('/'), \`\${appPath} web manifest start_url must be a public route path\`);
|
|
5126
|
+
};
|
|
4801
5127
|
const expectedWorkerName = packageSuffix => \`\${packageScope}-\${packageSuffix}\`.slice(0, 63);
|
|
4802
5128
|
const expectedChunkLoadingGlobal = mfName =>
|
|
4803
5129
|
\`__ULTRAMODERN_\${mfName
|
|
@@ -4874,6 +5200,9 @@ const requiredPaths = [
|
|
|
4874
5200
|
'apps/shell-super-app/src/routes/layout.tsx',
|
|
4875
5201
|
'apps/shell-super-app/src/routes/ultramodern-route-metadata.ts',
|
|
4876
5202
|
'apps/shell-super-app/src/routes/[lang]/page.tsx',
|
|
5203
|
+
...publicSurfaceRequiredAssetPaths.map(
|
|
5204
|
+
relativePath => \`apps/shell-super-app/\${relativePath}\`,
|
|
5205
|
+
),
|
|
4877
5206
|
'packages/shared-contracts/src/index.ts',
|
|
4878
5207
|
'packages/shared-design-tokens/src/index.ts',
|
|
4879
5208
|
'packages/shared-design-tokens/src/tokens.css',
|
|
@@ -4900,6 +5229,9 @@ for (const vertical of fullStackVerticals) {
|
|
|
4900
5229
|
\`\${vertical.path}/src/routes/layout.tsx\`,
|
|
4901
5230
|
\`\${vertical.path}/src/routes/ultramodern-route-metadata.ts\`,
|
|
4902
5231
|
\`\${vertical.path}/src/routes/[lang]/page.tsx\`,
|
|
5232
|
+
...publicSurfaceRequiredAssetPaths.map(
|
|
5233
|
+
relativePath => \`\${vertical.path}/\${relativePath}\`,
|
|
5234
|
+
),
|
|
4903
5235
|
...vertical.routePagePaths,
|
|
4904
5236
|
);
|
|
4905
5237
|
}
|
|
@@ -4934,6 +5266,31 @@ assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare
|
|
|
4934
5266
|
assert(rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json', 'Root must point at package source metadata');
|
|
4935
5267
|
assert(rootPackage.modernjs?.packageSource?.strategy === packageSource.strategy, 'Root package source strategy must match metadata');
|
|
4936
5268
|
assert(packageSource.strategy === 'workspace' || packageSource.strategy === 'install', 'Package source strategy must be workspace or install');
|
|
5269
|
+
assert(packageSource.strategy === 'install' || packageSource.modernPackages?.specifier === 'workspace:*', 'Workspace package source must be explicitly backed by workspace:*');
|
|
5270
|
+
if (packageSource.strategy === 'install') {
|
|
5271
|
+
const installSpecifier = packageSource.modernPackages?.specifier;
|
|
5272
|
+
assert(
|
|
5273
|
+
typeof installSpecifier === 'string' &&
|
|
5274
|
+
/^\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z.-]+)?(?:\\+[0-9A-Za-z.-]+)?$/.test(installSpecifier) &&
|
|
5275
|
+
installSpecifier.includes('ultramodern'),
|
|
5276
|
+
'Install package source must use a semver UltraModern published cohort',
|
|
5277
|
+
);
|
|
5278
|
+
const modernAliases = packageSource.modernPackages?.aliases ?? {};
|
|
5279
|
+
if (Object.keys(modernAliases).length > 0) {
|
|
5280
|
+
for (const modernPackageName of [
|
|
5281
|
+
'@modern-js/app-tools',
|
|
5282
|
+
'@modern-js/plugin-bff',
|
|
5283
|
+
'@modern-js/plugin-i18n',
|
|
5284
|
+
'@modern-js/plugin-tanstack',
|
|
5285
|
+
'@modern-js/runtime',
|
|
5286
|
+
]) {
|
|
5287
|
+
assert(
|
|
5288
|
+
/^@[^/]+\\/.+/.test(modernAliases[modernPackageName] ?? ''),
|
|
5289
|
+
\`Install package source alias for \${modernPackageName} must be a scoped npm package\`,
|
|
5290
|
+
);
|
|
5291
|
+
}
|
|
5292
|
+
}
|
|
5293
|
+
}
|
|
4937
5294
|
assert(packageSource.generatedWorkspacePackages?.specifier === 'workspace:*', 'Generated workspace packages must keep workspace:* links');
|
|
4938
5295
|
assert(
|
|
4939
5296
|
rootPackage.scripts?.build === expectedBuildScript,
|
|
@@ -4950,6 +5307,7 @@ assert(rootPackage.scripts?.['skills:check'] === 'node ./scripts/bootstrap-agent
|
|
|
4950
5307
|
assert(rootPackage.scripts?.postinstall === "oxfmt . '!repos/**' && node ./scripts/bootstrap-agent-skills.mjs && node ./scripts/setup-agent-reference-repos.mjs", 'Root postinstall must format, bootstrap agent skills, initialize git/hooks, and install reference repositories');
|
|
4951
5308
|
|
|
4952
5309
|
const expectedAppIds = ['shell-super-app', ...fullStackVerticals.map(vertical => vertical.id)];
|
|
5310
|
+
const expectedCloudflareCompatibilityDate = '${CLOUDFLARE_COMPATIBILITY_DATE}';
|
|
4953
5311
|
const expectedCloudflareCompatibilityFlags = ['nodejs_compat', 'global_fetch_strictly_public'];
|
|
4954
5312
|
assert(
|
|
4955
5313
|
JSON.stringify(generatedContract.apps?.map(app => app.id)) === JSON.stringify(expectedAppIds),
|
|
@@ -4966,6 +5324,7 @@ assert(generatedContract.cssFederation?.sharedDesignTokens?.dedupe?.duplicateBas
|
|
|
4966
5324
|
assert(generatedContract.cssFederation?.sharedDesignTokens?.ssr?.firstPaintRequired === true, 'Shared design token CSS must be required for SSR first paint');
|
|
4967
5325
|
|
|
4968
5326
|
const shellPackage = readJson('apps/shell-super-app/package.json');
|
|
5327
|
+
const shellModernConfig = readText('apps/shell-super-app/modern.config.ts');
|
|
4969
5328
|
const expectedZephyrDependencies = Object.fromEntries(
|
|
4970
5329
|
fullStackVerticals.map(vertical => [
|
|
4971
5330
|
vertical.zephyrAlias,
|
|
@@ -4977,10 +5336,21 @@ assert(
|
|
|
4977
5336
|
JSON.stringify(expectedZephyrDependencies),
|
|
4978
5337
|
'Shell Zephyr dependencies must reference every vertical package',
|
|
4979
5338
|
);
|
|
5339
|
+
assert(shellPackage.devDependencies?.['@modern-js/app-tools'] === expectedModernPackageSpecifier('@modern-js/app-tools'), 'Shell app-tools dependency must match package source metadata');
|
|
5340
|
+
assert(shellPackage.dependencies?.['@modern-js/plugin-bff'] === expectedModernPackageSpecifier('@modern-js/plugin-bff'), 'Shell plugin-bff dependency must match package source metadata');
|
|
5341
|
+
assert(shellPackage.dependencies?.['@modern-js/plugin-i18n'] === expectedModernPackageSpecifier('@modern-js/plugin-i18n'), 'Shell plugin-i18n dependency must match package source metadata');
|
|
5342
|
+
assert(shellPackage.dependencies?.['@modern-js/plugin-tanstack'] === expectedModernPackageSpecifier('@modern-js/plugin-tanstack'), 'Shell plugin-tanstack dependency must match package source metadata');
|
|
5343
|
+
assert(shellPackage.dependencies?.['@modern-js/runtime'] === expectedModernPackageSpecifier('@modern-js/runtime'), 'Shell runtime dependency must match package source metadata');
|
|
4980
5344
|
const shellContract = generatedContract.apps?.find(app => app.id === 'shell-super-app');
|
|
4981
5345
|
assert(shellContract?.deploy?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell Cloudflare workerName is incorrect');
|
|
4982
5346
|
assert(shellContract?.deploy?.cloudflare?.publicUrlEnv === 'ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP', 'Shell Cloudflare public URL env is incorrect');
|
|
5347
|
+
assert(shellContract?.deploy?.cloudflare?.compatibilityDate === expectedCloudflareCompatibilityDate, 'Shell Cloudflare compatibilityDate is incorrect');
|
|
4983
5348
|
assert(JSON.stringify(shellContract?.deploy?.cloudflare?.compatibilityFlags) === JSON.stringify(expectedCloudflareCompatibilityFlags), 'Shell Cloudflare compatibility flags are incorrect');
|
|
5349
|
+
assert(JSON.stringify(shellContract?.deploy?.cloudflare?.security) === JSON.stringify(expectedCloudflareSecurity), 'Shell Cloudflare security contract is incorrect');
|
|
5350
|
+
assert(shellContract?.deploy?.worker?.compatibilityDate === expectedCloudflareCompatibilityDate, 'Shell worker compatibilityDate is incorrect');
|
|
5351
|
+
assert(shellContract?.deploy?.worker?.name === expectedWorkerName('shell-super-app'), 'Shell worker name is incorrect');
|
|
5352
|
+
assert(shellModernConfig.includes("const cloudflareWorkerName = '" + expectedWorkerName('shell-super-app') + "'"), 'Shell modern.config.ts must define the Cloudflare worker name');
|
|
5353
|
+
assert(shellModernConfig.includes('name: cloudflareWorkerName'), 'Shell modern.config.ts must wire deploy.worker.name');
|
|
4984
5354
|
assert(shellContract?.config?.rspack?.output?.uniqueName === 'shellSuperApp', 'Shell Rspack uniqueName is incorrect');
|
|
4985
5355
|
assert(shellContract?.config?.rspack?.output?.chunkLoadingGlobal === expectedChunkLoadingGlobal('shellSuperApp'), 'Shell Rspack chunkLoadingGlobal is incorrect');
|
|
4986
5356
|
assert(topology.shell?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell topology Cloudflare workerName is incorrect');
|
|
@@ -4994,6 +5364,14 @@ assert(shellContract?.styling?.federation?.entrypoints?.css?.includes('src/route
|
|
|
4994
5364
|
assert(shellContract?.styling?.federation?.assets?.shared?.some(asset => asset.endsWith('/shared-design-tokens/tokens.css')), 'Shell must import the shared design token CSS asset');
|
|
4995
5365
|
assert(shellContract?.styling?.federation?.dedupe?.duplicateBaseStylesAllowed === false, 'Shell CSS contract must forbid duplicated base styles');
|
|
4996
5366
|
assert(shellContract?.styling?.federation?.ssr?.firstPaintRequired === true, 'Shell CSS must be required for SSR first paint');
|
|
5367
|
+
assert(shellContract?.routes?.privateByDefault === true, 'Shell routes must be private by default');
|
|
5368
|
+
assert(shellContract?.routes?.publicnessDefault === 'private-app-screen', 'Shell route publicness default is incorrect');
|
|
5369
|
+
assert(JSON.stringify(shellContract?.routes?.publicRoutes ?? []) === '[]', 'Shell must not expose generated public routes by default');
|
|
5370
|
+
assert(
|
|
5371
|
+
(shellContract?.routes?.owned ?? []).every(route => route.public === false && route.indexable === false && route.publicSurface === 'private-app-screen'),
|
|
5372
|
+
'Shell owned routes must be non-indexable private app screens by default',
|
|
5373
|
+
);
|
|
5374
|
+
assertPublicSurfaceAssets('apps/shell-super-app', shellContract?.routes?.publicRoutes ?? []);
|
|
4997
5375
|
assert(
|
|
4998
5376
|
topology.shell?.verticalRefs?.join(',') === fullStackVerticals.map(vertical => vertical.id).join(','),
|
|
4999
5377
|
'Topology shell verticalRefs must match generated verticals',
|
|
@@ -5004,10 +5382,15 @@ assert(!('effectServices' in topology), 'Default APIs must be vertical-owned, no
|
|
|
5004
5382
|
|
|
5005
5383
|
for (const vertical of fullStackVerticals) {
|
|
5006
5384
|
const packageJson = readJson(\`\${vertical.path}/package.json\`);
|
|
5385
|
+
const modernConfig = readText(\`\${vertical.path}/modern.config.ts\`);
|
|
5007
5386
|
assert(packageJson.name === vertical.packageName, \`\${vertical.id} package name is incorrect\`);
|
|
5008
5387
|
assert(packageJson.scripts?.['cloudflare:deploy'] === 'ULTRAMODERN_CLOUDFLARE_REQUIRE_PUBLIC_URLS=true pnpm run cloudflare:build && wrangler deploy --config .output/wrangler.json', \`\${vertical.id} must expose cloudflare:deploy\`);
|
|
5009
5388
|
assert(packageJson.scripts?.['cloudflare:proof']?.includes(\`--app \${vertical.id}\`), \`\${vertical.id} must expose cloudflare:proof\`);
|
|
5010
|
-
assert(packageJson.
|
|
5389
|
+
assert(packageJson.devDependencies?.['@modern-js/app-tools'] === expectedModernPackageSpecifier('@modern-js/app-tools'), \`\${vertical.id} app-tools dependency must match package source metadata\`);
|
|
5390
|
+
assert(packageJson.dependencies?.['@modern-js/plugin-bff'] === expectedModernPackageSpecifier('@modern-js/plugin-bff'), \`\${vertical.id} plugin-bff dependency must match package source metadata\`);
|
|
5391
|
+
assert(packageJson.dependencies?.['@modern-js/plugin-i18n'] === expectedModernPackageSpecifier('@modern-js/plugin-i18n'), \`\${vertical.id} plugin-i18n dependency must match package source metadata\`);
|
|
5392
|
+
assert(packageJson.dependencies?.['@modern-js/plugin-tanstack'] === expectedModernPackageSpecifier('@modern-js/plugin-tanstack'), \`\${vertical.id} plugin-tanstack dependency must match package source metadata\`);
|
|
5393
|
+
assert(packageJson.dependencies?.['@modern-js/runtime'] === expectedModernPackageSpecifier('@modern-js/runtime'), \`\${vertical.id} runtime dependency must match package source metadata\`);
|
|
5011
5394
|
assert(packageJson.exports?.['./effect/client'] === \`./src/effect/\${vertical.stem}-client.ts\`, \`\${vertical.id} must export its Effect client\`);
|
|
5012
5395
|
assert(packageJson.exports?.['./shared/effect/api'] === './shared/effect/api.ts', \`\${vertical.id} must export its Effect API contract\`);
|
|
5013
5396
|
const expectedVerticalZephyrDependencies = Object.fromEntries(
|
|
@@ -5029,7 +5412,13 @@ for (const vertical of fullStackVerticals) {
|
|
|
5029
5412
|
assert(contractEntry?.kind === 'vertical', \`\${vertical.id} generated contract kind is incorrect\`);
|
|
5030
5413
|
assert(contractEntry?.deploy?.cloudflare?.workerName === expectedWorkerName(vertical.id), \`\${vertical.id} Cloudflare workerName is incorrect\`);
|
|
5031
5414
|
assert(contractEntry?.deploy?.cloudflare?.publicUrlEnv === \`ULTRAMODERN_PUBLIC_URL_\${vertical.id.replace(/-/g, '_').toUpperCase()}\`, \`\${vertical.id} Cloudflare public URL env is incorrect\`);
|
|
5415
|
+
assert(contractEntry?.deploy?.cloudflare?.compatibilityDate === expectedCloudflareCompatibilityDate, \`\${vertical.id} Cloudflare compatibilityDate is incorrect\`);
|
|
5032
5416
|
assert(JSON.stringify(contractEntry?.deploy?.cloudflare?.compatibilityFlags) === JSON.stringify(expectedCloudflareCompatibilityFlags), \`\${vertical.id} Cloudflare compatibility flags are incorrect\`);
|
|
5417
|
+
assert(JSON.stringify(contractEntry?.deploy?.cloudflare?.security) === JSON.stringify(expectedCloudflareSecurity), \`\${vertical.id} Cloudflare security contract is incorrect\`);
|
|
5418
|
+
assert(contractEntry?.deploy?.worker?.compatibilityDate === expectedCloudflareCompatibilityDate, \`\${vertical.id} worker compatibilityDate is incorrect\`);
|
|
5419
|
+
assert(contractEntry?.deploy?.worker?.name === expectedWorkerName(vertical.id), \`\${vertical.id} worker name is incorrect\`);
|
|
5420
|
+
assert(modernConfig.includes("const cloudflareWorkerName = '" + expectedWorkerName(vertical.id) + "'"), \`\${vertical.id} modern.config.ts must define the Cloudflare worker name\`);
|
|
5421
|
+
assert(modernConfig.includes('name: cloudflareWorkerName'), \`\${vertical.id} modern.config.ts must wire deploy.worker.name\`);
|
|
5033
5422
|
assert(contractEntry?.deploy?.cloudflare?.routes?.effectReadiness === \`\${vertical.apiPrefix}/effect/\${vertical.stem}/readiness\`, \`\${vertical.id} Cloudflare proof readiness route is incorrect\`);
|
|
5034
5423
|
assert(contractEntry?.config?.rspack?.output?.uniqueName === vertical.mfName, \`\${vertical.id} Rspack uniqueName is incorrect\`);
|
|
5035
5424
|
assert(contractEntry?.config?.rspack?.output?.chunkLoadingGlobal === expectedChunkLoadingGlobal(vertical.mfName), \`\${vertical.id} Rspack chunkLoadingGlobal is incorrect\`);
|
|
@@ -5056,6 +5445,14 @@ for (const vertical of fullStackVerticals) {
|
|
|
5056
5445
|
);
|
|
5057
5446
|
assert(contractEntry?.routes?.source === 'route-owned', \`\${vertical.id} routes must be route-owned\`);
|
|
5058
5447
|
assert(contractEntry?.routes?.metadataExport === './src/routes/ultramodern-route-metadata', \`\${vertical.id} route metadata export is incorrect\`);
|
|
5448
|
+
assert(contractEntry?.routes?.privateByDefault === true, \`\${vertical.id} routes must be private by default\`);
|
|
5449
|
+
assert(contractEntry?.routes?.publicnessDefault === 'private-app-screen', \`\${vertical.id} route publicness default is incorrect\`);
|
|
5450
|
+
assert(JSON.stringify(contractEntry?.routes?.publicRoutes ?? []) === '[]', \`\${vertical.id} must not expose generated public routes by default\`);
|
|
5451
|
+
assert(
|
|
5452
|
+
(contractEntry?.routes?.owned ?? []).every(route => route.public === false && route.indexable === false && route.publicSurface === 'private-app-screen'),
|
|
5453
|
+
\`\${vertical.id} owned routes must be non-indexable private app screens by default\`,
|
|
5454
|
+
);
|
|
5455
|
+
assertPublicSurfaceAssets(vertical.path, contractEntry?.routes?.publicRoutes ?? []);
|
|
5059
5456
|
assert(contractEntry?.styling?.federation?.owner?.id === vertical.id, \`\${vertical.id} CSS federation owner is missing\`);
|
|
5060
5457
|
assert(contractEntry?.styling?.federation?.role === 'vertical-css', \`\${vertical.id} must own only vertical CSS\`);
|
|
5061
5458
|
assert(contractEntry?.styling?.federation?.rootSelector === \`[data-app-id="\${vertical.id}"]\`, \`\${vertical.id} CSS root selector is incorrect\`);
|
|
@@ -5161,8 +5558,14 @@ async function fetchText(url) {
|
|
|
5161
5558
|
ok: response.ok,
|
|
5162
5559
|
status: response.status,
|
|
5163
5560
|
accessControlAllowOrigin: response.headers.get('access-control-allow-origin'),
|
|
5561
|
+
contentSecurityPolicy: response.headers.get('content-security-policy'),
|
|
5562
|
+
contentSecurityPolicyReportOnly: response.headers.get('content-security-policy-report-only'),
|
|
5164
5563
|
contentType: response.headers.get('content-type'),
|
|
5165
5564
|
link: response.headers.get('link'),
|
|
5565
|
+
permissionsPolicy: response.headers.get('permissions-policy'),
|
|
5566
|
+
referrerPolicy: response.headers.get('referrer-policy'),
|
|
5567
|
+
xContentTypeOptions: response.headers.get('x-content-type-options'),
|
|
5568
|
+
xRobotsTag: response.headers.get('x-robots-tag'),
|
|
5166
5569
|
body: await response.text(),
|
|
5167
5570
|
};
|
|
5168
5571
|
}
|
|
@@ -5213,6 +5616,139 @@ function assert(condition, message) {
|
|
|
5213
5616
|
}
|
|
5214
5617
|
}
|
|
5215
5618
|
|
|
5619
|
+
function matchesPreviewHostname(hostname, pattern) {
|
|
5620
|
+
const normalizedHostname = hostname.toLowerCase();
|
|
5621
|
+
const normalizedPattern = String(pattern || '').toLowerCase();
|
|
5622
|
+
|
|
5623
|
+
if (!normalizedPattern) {
|
|
5624
|
+
return false;
|
|
5625
|
+
}
|
|
5626
|
+
|
|
5627
|
+
if (normalizedPattern.startsWith('*.')) {
|
|
5628
|
+
return normalizedHostname.endsWith(normalizedPattern.slice(1));
|
|
5629
|
+
}
|
|
5630
|
+
|
|
5631
|
+
return normalizedHostname === normalizedPattern;
|
|
5632
|
+
}
|
|
5633
|
+
|
|
5634
|
+
function shouldNoindexUrl(publicUrl, noindex) {
|
|
5635
|
+
if (!noindex || noindex === false) {
|
|
5636
|
+
return false;
|
|
5637
|
+
}
|
|
5638
|
+
|
|
5639
|
+
const { hostname } = new URL(publicUrl);
|
|
5640
|
+
const normalizedHostname = hostname.toLowerCase();
|
|
5641
|
+
|
|
5642
|
+
if (
|
|
5643
|
+
noindex.localhost !== false &&
|
|
5644
|
+
(normalizedHostname === 'localhost' ||
|
|
5645
|
+
normalizedHostname === '127.0.0.1' ||
|
|
5646
|
+
normalizedHostname === '[::1]')
|
|
5647
|
+
) {
|
|
5648
|
+
return true;
|
|
5649
|
+
}
|
|
5650
|
+
|
|
5651
|
+
if (
|
|
5652
|
+
noindex.workersDev !== false &&
|
|
5653
|
+
normalizedHostname.endsWith('.workers.dev')
|
|
5654
|
+
) {
|
|
5655
|
+
return true;
|
|
5656
|
+
}
|
|
5657
|
+
|
|
5658
|
+
return (noindex.previewHostnames || []).some(pattern =>
|
|
5659
|
+
matchesPreviewHostname(normalizedHostname, pattern),
|
|
5660
|
+
);
|
|
5661
|
+
}
|
|
5662
|
+
|
|
5663
|
+
function assertHeader(evidence, response, expected, options) {
|
|
5664
|
+
if (expected === false || expected === undefined) {
|
|
5665
|
+
return;
|
|
5666
|
+
}
|
|
5667
|
+
|
|
5668
|
+
const actual = response[options.field];
|
|
5669
|
+
evidence.assertions.push({
|
|
5670
|
+
type: 'security-header',
|
|
5671
|
+
header: options.header,
|
|
5672
|
+
route: options.route,
|
|
5673
|
+
expected,
|
|
5674
|
+
actual,
|
|
5675
|
+
status: actual === expected ? 'pass' : 'fail',
|
|
5676
|
+
});
|
|
5677
|
+
assert(actual === expected, \`\${options.appId} \${options.route} is missing \${options.header}\`);
|
|
5678
|
+
}
|
|
5679
|
+
|
|
5680
|
+
function assertCloudflareSecurity(evidence, app, response, route, publicUrl, options = {}) {
|
|
5681
|
+
const security = app.deploy?.cloudflare?.security;
|
|
5682
|
+
|
|
5683
|
+
if (!security || security.enabled === false) {
|
|
5684
|
+
return;
|
|
5685
|
+
}
|
|
5686
|
+
|
|
5687
|
+
const headers = security.headers || {};
|
|
5688
|
+
assertHeader(evidence, response, headers.referrerPolicy, {
|
|
5689
|
+
appId: app.id,
|
|
5690
|
+
field: 'referrerPolicy',
|
|
5691
|
+
header: 'referrer-policy',
|
|
5692
|
+
route,
|
|
5693
|
+
});
|
|
5694
|
+
assertHeader(evidence, response, headers.contentTypeOptions, {
|
|
5695
|
+
appId: app.id,
|
|
5696
|
+
field: 'xContentTypeOptions',
|
|
5697
|
+
header: 'x-content-type-options',
|
|
5698
|
+
route,
|
|
5699
|
+
});
|
|
5700
|
+
assertHeader(evidence, response, headers.permissionsPolicy, {
|
|
5701
|
+
appId: app.id,
|
|
5702
|
+
field: 'permissionsPolicy',
|
|
5703
|
+
header: 'permissions-policy',
|
|
5704
|
+
route,
|
|
5705
|
+
});
|
|
5706
|
+
|
|
5707
|
+
const csp = security.contentSecurityPolicy;
|
|
5708
|
+
if (options.html && csp?.mode !== 'off') {
|
|
5709
|
+
const header =
|
|
5710
|
+
csp?.mode === 'enforce'
|
|
5711
|
+
? 'content-security-policy'
|
|
5712
|
+
: 'content-security-policy-report-only';
|
|
5713
|
+
const actual =
|
|
5714
|
+
csp?.mode === 'enforce'
|
|
5715
|
+
? response.contentSecurityPolicy
|
|
5716
|
+
: response.contentSecurityPolicyReportOnly;
|
|
5717
|
+
const expectedDirectives = ['script-src', 'style-src', 'connect-src'];
|
|
5718
|
+
const missingDirectives = expectedDirectives.filter(
|
|
5719
|
+
directive => !actual?.includes(directive),
|
|
5720
|
+
);
|
|
5721
|
+
|
|
5722
|
+
evidence.assertions.push({
|
|
5723
|
+
type: 'security-csp',
|
|
5724
|
+
header,
|
|
5725
|
+
route,
|
|
5726
|
+
mode: csp?.mode ?? 'report-only',
|
|
5727
|
+
actual,
|
|
5728
|
+
missingDirectives,
|
|
5729
|
+
status: actual && missingDirectives.length === 0 ? 'pass' : 'fail',
|
|
5730
|
+
});
|
|
5731
|
+
assert(actual, \`\${app.id} \${route} is missing \${header}\`);
|
|
5732
|
+
assert(
|
|
5733
|
+
missingDirectives.length === 0,
|
|
5734
|
+
\`\${app.id} \${route} CSP is missing \${missingDirectives.join(', ')}\`,
|
|
5735
|
+
);
|
|
5736
|
+
}
|
|
5737
|
+
|
|
5738
|
+
if (shouldNoindexUrl(publicUrl, security.noindex)) {
|
|
5739
|
+
evidence.assertions.push({
|
|
5740
|
+
type: 'security-noindex',
|
|
5741
|
+
route,
|
|
5742
|
+
actual: response.xRobotsTag,
|
|
5743
|
+
status: response.xRobotsTag === 'noindex, nofollow' ? 'pass' : 'fail',
|
|
5744
|
+
});
|
|
5745
|
+
assert(
|
|
5746
|
+
response.xRobotsTag === 'noindex, nofollow',
|
|
5747
|
+
\`\${app.id} \${route} is missing noindex X-Robots-Tag\`,
|
|
5748
|
+
);
|
|
5749
|
+
}
|
|
5750
|
+
}
|
|
5751
|
+
|
|
5216
5752
|
async function validateApp(app, publicUrl) {
|
|
5217
5753
|
const cloudflare = app.deploy?.cloudflare;
|
|
5218
5754
|
const routes = cloudflare?.routes ?? {};
|
|
@@ -5233,6 +5769,9 @@ async function validateApp(app, publicUrl) {
|
|
|
5233
5769
|
statusCode: ssr.status,
|
|
5234
5770
|
});
|
|
5235
5771
|
assert(ssr.ok, \`\${app.id} SSR route returned HTTP \${ssr.status}\`);
|
|
5772
|
+
assertCloudflareSecurity(evidence, app, ssr, ssrRoute, publicUrl, {
|
|
5773
|
+
html: true,
|
|
5774
|
+
});
|
|
5236
5775
|
|
|
5237
5776
|
const uiMarker = extractUiMarker(ssr.body);
|
|
5238
5777
|
evidence.assertions.push({
|
|
@@ -5286,6 +5825,7 @@ async function validateApp(app, publicUrl) {
|
|
|
5286
5825
|
manifest.ok,
|
|
5287
5826
|
\`\${app.id} MF manifest returned HTTP \${manifest.status}\`,
|
|
5288
5827
|
);
|
|
5828
|
+
assertCloudflareSecurity(evidence, app, manifest, manifestRoute, publicUrl);
|
|
5289
5829
|
evidence.assertions.push({
|
|
5290
5830
|
type: 'mf-manifest-cors',
|
|
5291
5831
|
route: manifestRoute,
|
|
@@ -5325,6 +5865,7 @@ async function validateApp(app, publicUrl) {
|
|
|
5325
5865
|
statusCode: locale.status,
|
|
5326
5866
|
});
|
|
5327
5867
|
assert(locale.ok, \`\${app.id} locale JSON returned HTTP \${locale.status}\`);
|
|
5868
|
+
assertCloudflareSecurity(evidence, app, locale, localeRoute, publicUrl);
|
|
5328
5869
|
evidence.assertions.push({
|
|
5329
5870
|
type: 'i18n-cors',
|
|
5330
5871
|
route: localeRoute,
|
|
@@ -5570,6 +6111,7 @@ function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwin
|
|
|
5570
6111
|
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/package.json`), createAppPackage(scope, shellHost, packageSource, enableTailwind, remotes));
|
|
5571
6112
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern-app-env.d.ts`, createAppEnvDts(shellHost, remotes));
|
|
5572
6113
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/ultramodern-route-metadata.ts`, createRouteMetadataModule(shellHost));
|
|
6114
|
+
rewriteWorkspaceAssetsForApp(workspaceRoot, shellHost);
|
|
5573
6115
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(shellHost, scope, remotes));
|
|
5574
6116
|
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/translation.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
|
|
5575
6117
|
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/${appI18nNamespace(shellHost)}.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
|
|
@@ -6138,7 +6680,7 @@ function readCreatePackageJson() {
|
|
|
6138
6680
|
return JSON.parse(node_fs.readFileSync(createPackageJson, 'utf-8'));
|
|
6139
6681
|
}
|
|
6140
6682
|
function isBleedingDevCreatePackage(createPackage) {
|
|
6141
|
-
return
|
|
6683
|
+
return createPackage.name === BLEEDINGDEV_CREATE_PACKAGE;
|
|
6142
6684
|
}
|
|
6143
6685
|
function getBleedingDevFrameworkVersion(createPackage, fallbackVersion) {
|
|
6144
6686
|
const frameworkVersion = createPackage.ultramodern?.frameworkVersion;
|
|
@@ -6245,71 +6787,109 @@ function detectUltramodernPackageSource(args, defaultPackageVersion, createPacka
|
|
|
6245
6787
|
console.error('--ultramodern-package-source must be "workspace" or "install"');
|
|
6246
6788
|
process.exit(1);
|
|
6247
6789
|
}
|
|
6790
|
+
const packageSourceStrategy = strategy;
|
|
6791
|
+
const explicitRegistry = getOptionValue(args, [
|
|
6792
|
+
'--ultramodern-package-registry'
|
|
6793
|
+
]);
|
|
6794
|
+
const aliasScope = getOptionValue(args, [
|
|
6795
|
+
'--ultramodern-package-scope'
|
|
6796
|
+
]) ?? (bleedingDevDefaults && 'install' === packageSourceStrategy && !explicitRegistry ? BLEEDINGDEV_PACKAGE_SCOPE : void 0);
|
|
6248
6797
|
return {
|
|
6249
|
-
strategy,
|
|
6798
|
+
strategy: packageSourceStrategy,
|
|
6250
6799
|
modernPackageVersion: getOptionValue(args, [
|
|
6251
6800
|
'--ultramodern-package-version'
|
|
6252
6801
|
]) ?? defaultPackageVersion,
|
|
6253
|
-
registry:
|
|
6254
|
-
|
|
6255
|
-
]),
|
|
6256
|
-
aliasScope: getOptionValue(args, [
|
|
6257
|
-
'--ultramodern-package-scope'
|
|
6258
|
-
]) ?? (bleedingDevDefaults && 'install' === strategy ? 'bleedingdev' : void 0),
|
|
6802
|
+
registry: explicitRegistry,
|
|
6803
|
+
aliasScope,
|
|
6259
6804
|
aliasPackageNamePrefix: getOptionValue(args, [
|
|
6260
6805
|
'--ultramodern-package-name-prefix'
|
|
6261
|
-
]) ??
|
|
6806
|
+
]) ?? (aliasScope ? BLEEDINGDEV_PACKAGE_NAME_PREFIX : void 0)
|
|
6262
6807
|
};
|
|
6263
6808
|
}
|
|
6264
|
-
function
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
return
|
|
6809
|
+
function hasExplicitUltramodernPackageSource(args, value) {
|
|
6810
|
+
const configuredValue = getOptionValue(args, [
|
|
6811
|
+
'--ultramodern-package-source'
|
|
6812
|
+
]);
|
|
6813
|
+
return value ? configuredValue === value : void 0 !== configuredValue;
|
|
6269
6814
|
}
|
|
6270
|
-
function
|
|
6271
|
-
|
|
6272
|
-
if (
|
|
6273
|
-
|
|
6815
|
+
function readBleedingDevFrameworkVersionFromRegistry() {
|
|
6816
|
+
const envVersion = process.env[BLEEDINGDEV_FRAMEWORK_VERSION_ENV]?.trim();
|
|
6817
|
+
if (envVersion) {
|
|
6818
|
+
if (!semverPattern.test(envVersion)) {
|
|
6819
|
+
console.error(`${BLEEDINGDEV_FRAMEWORK_VERSION_ENV} must be a valid semver version`);
|
|
6820
|
+
process.exit(1);
|
|
6821
|
+
}
|
|
6822
|
+
return envVersion;
|
|
6823
|
+
}
|
|
6824
|
+
try {
|
|
6825
|
+
const rawVersion = runSetupCommand('npm', [
|
|
6826
|
+
'view',
|
|
6827
|
+
`${BLEEDINGDEV_CREATE_PACKAGE}@latest`,
|
|
6828
|
+
'ultramodern.frameworkVersion',
|
|
6829
|
+
'--json'
|
|
6830
|
+
]).trim();
|
|
6831
|
+
const version = JSON.parse(rawVersion);
|
|
6832
|
+
if ('string' == typeof version && semverPattern.test(version)) return version;
|
|
6833
|
+
} catch {}
|
|
6834
|
+
console.error([
|
|
6835
|
+
`Could not resolve ${BLEEDINGDEV_CREATE_PACKAGE}@latest ultramodern.frameworkVersion.`,
|
|
6836
|
+
'Pass --workspace to use local workspace protocol dependencies,',
|
|
6837
|
+
'or pass --ultramodern-package-version with the exact BleedingDev framework cohort.'
|
|
6838
|
+
].join(' '));
|
|
6839
|
+
process.exit(1);
|
|
6274
6840
|
}
|
|
6275
|
-
|
|
6276
|
-
|
|
6277
|
-
|
|
6278
|
-
|
|
6279
|
-
|
|
6280
|
-
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6841
|
+
function resolveInstallBackedPackageSource(args, createPackage, packageSource) {
|
|
6842
|
+
const explicitVersion = getOptionValue(args, [
|
|
6843
|
+
'--ultramodern-package-version'
|
|
6844
|
+
]);
|
|
6845
|
+
const explicitRegistry = getOptionValue(args, [
|
|
6846
|
+
'--ultramodern-package-registry'
|
|
6847
|
+
]);
|
|
6848
|
+
const aliasScope = getOptionValue(args, [
|
|
6849
|
+
'--ultramodern-package-scope'
|
|
6850
|
+
]) ?? packageSource.aliasScope ?? (explicitRegistry ? void 0 : BLEEDINGDEV_PACKAGE_SCOPE);
|
|
6851
|
+
return {
|
|
6852
|
+
...packageSource,
|
|
6853
|
+
strategy: 'install',
|
|
6854
|
+
modernPackageVersion: explicitVersion ?? (isBleedingDevCreatePackage(createPackage) ? packageSource.modernPackageVersion : readBleedingDevFrameworkVersionFromRegistry()),
|
|
6855
|
+
aliasScope,
|
|
6856
|
+
aliasPackageNamePrefix: getOptionValue(args, [
|
|
6857
|
+
'--ultramodern-package-name-prefix'
|
|
6858
|
+
]) ?? packageSource.aliasPackageNamePrefix ?? (aliasScope ? BLEEDINGDEV_PACKAGE_NAME_PREFIX : void 0)
|
|
6859
|
+
};
|
|
6860
|
+
}
|
|
6861
|
+
function resolveSingleAppPackageSource(args, createPackage, packageSource, useWorkspaceProtocol) {
|
|
6862
|
+
if (useWorkspaceProtocol) return {
|
|
6863
|
+
...packageSource,
|
|
6864
|
+
strategy: 'workspace',
|
|
6865
|
+
modernPackageVersion: WORKSPACE_PACKAGE_VERSION
|
|
6866
|
+
};
|
|
6867
|
+
return resolveInstallBackedPackageSource(args, createPackage, packageSource);
|
|
6868
|
+
}
|
|
6869
|
+
function resolveWorkspacePackageSource(args, createPackage, packageSource) {
|
|
6870
|
+
if (hasExplicitUltramodernPackageSource(args, 'workspace')) return {
|
|
6871
|
+
...packageSource,
|
|
6872
|
+
strategy: 'workspace',
|
|
6873
|
+
modernPackageVersion: WORKSPACE_PACKAGE_VERSION
|
|
6874
|
+
};
|
|
6875
|
+
return resolveInstallBackedPackageSource(args, createPackage, packageSource);
|
|
6876
|
+
}
|
|
6877
|
+
function createSingleAppPackageSourceEvidence(packageSource) {
|
|
6291
6878
|
return {
|
|
6292
6879
|
schemaVersion: 1,
|
|
6293
6880
|
preset: 'presetUltramodern',
|
|
6294
|
-
strategy,
|
|
6295
|
-
modernPackages: {
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
...packageSource.registry ? {
|
|
6299
|
-
registry: packageSource.registry
|
|
6300
|
-
} : {},
|
|
6301
|
-
...aliases ? {
|
|
6302
|
-
aliases
|
|
6303
|
-
} : {}
|
|
6304
|
-
}
|
|
6881
|
+
strategy: packageSource.strategy,
|
|
6882
|
+
modernPackages: createModernPackagesMetadata(ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES, packageSource, {
|
|
6883
|
+
includeAliases: 'install' === packageSource.strategy
|
|
6884
|
+
})
|
|
6305
6885
|
};
|
|
6306
6886
|
}
|
|
6307
|
-
function writeSingleAppPackageSourceEvidence(targetDir, packageSource
|
|
6887
|
+
function writeSingleAppPackageSourceEvidence(targetDir, packageSource) {
|
|
6308
6888
|
const evidencePath = node_path.join(targetDir, '.modernjs', 'ultramodern-package-source.json');
|
|
6309
6889
|
node_fs.mkdirSync(node_path.dirname(evidencePath), {
|
|
6310
6890
|
recursive: true
|
|
6311
6891
|
});
|
|
6312
|
-
node_fs.writeFileSync(evidencePath, `${JSON.stringify(createSingleAppPackageSourceEvidence(packageSource
|
|
6892
|
+
node_fs.writeFileSync(evidencePath, `${JSON.stringify(createSingleAppPackageSourceEvidence(packageSource), null, 2)}\n`);
|
|
6313
6893
|
}
|
|
6314
6894
|
function runSetupCommand(command, args, options = {}) {
|
|
6315
6895
|
return execFileSync(command, args, {
|
|
@@ -6514,12 +7094,13 @@ async function main() {
|
|
|
6514
7094
|
}
|
|
6515
7095
|
const generateWorkspace = detectUltramodernWorkspaceFlag();
|
|
6516
7096
|
if (generateWorkspace) {
|
|
7097
|
+
const packageSource = resolveWorkspacePackageSource(args, createPackage, detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage));
|
|
6517
7098
|
generateUltramodernWorkspace({
|
|
6518
7099
|
targetDir,
|
|
6519
7100
|
packageName: generatedPackageName,
|
|
6520
7101
|
modernVersion: version,
|
|
6521
7102
|
enableTailwind: detectTailwindFlag(),
|
|
6522
|
-
packageSource
|
|
7103
|
+
packageSource
|
|
6523
7104
|
});
|
|
6524
7105
|
initializeGeneratedGitRepository(targetDir);
|
|
6525
7106
|
const dim = '\x1b[2m\x1b[3m';
|
|
@@ -6540,19 +7121,19 @@ async function main() {
|
|
|
6540
7121
|
const bffRuntime = detectBffRuntime();
|
|
6541
7122
|
const enableTailwind = detectTailwindFlag();
|
|
6542
7123
|
const useWorkspaceProtocol = detectWorkspaceProtocolFlag();
|
|
6543
|
-
const packageSource = detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage);
|
|
6544
|
-
const templateManifest = createBuiltinTemplateManifest(version);
|
|
7124
|
+
const packageSource = resolveSingleAppPackageSource(args, createPackage, detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage), useWorkspaceProtocol);
|
|
7125
|
+
const templateManifest = createBuiltinTemplateManifest('install' === packageSource.strategy ? packageSource.modernPackageVersion : version);
|
|
6545
7126
|
validateTemplateManifest(templateManifest);
|
|
6546
7127
|
copyTemplate(templateDir, targetDir, {
|
|
6547
7128
|
packageName: generatedPackageName,
|
|
6548
|
-
version:
|
|
6549
|
-
runtimeVersion:
|
|
6550
|
-
appToolsVersion:
|
|
6551
|
-
adapterRstestVersion:
|
|
6552
|
-
tsconfigVersion:
|
|
6553
|
-
pluginTanstackVersion:
|
|
6554
|
-
pluginBffVersion:
|
|
6555
|
-
pluginI18nVersion:
|
|
7129
|
+
version: 'workspace' === packageSource.strategy ? WORKSPACE_PACKAGE_VERSION : packageSource.modernPackageVersion,
|
|
7130
|
+
runtimeVersion: modernPackageSpecifier('@modern-js/runtime', packageSource),
|
|
7131
|
+
appToolsVersion: modernPackageSpecifier('@modern-js/app-tools', packageSource),
|
|
7132
|
+
adapterRstestVersion: modernPackageSpecifier('@modern-js/adapter-rstest', packageSource),
|
|
7133
|
+
tsconfigVersion: modernPackageSpecifier('@modern-js/tsconfig', packageSource),
|
|
7134
|
+
pluginTanstackVersion: modernPackageSpecifier('@modern-js/plugin-tanstack', packageSource),
|
|
7135
|
+
pluginBffVersion: modernPackageSpecifier('@modern-js/plugin-bff', packageSource),
|
|
7136
|
+
pluginI18nVersion: modernPackageSpecifier('@modern-js/plugin-i18n', packageSource),
|
|
6556
7137
|
tanstackRouterVersion: src_TANSTACK_ROUTER_VERSION,
|
|
6557
7138
|
tailwindVersion: src_TAILWIND_VERSION,
|
|
6558
7139
|
tailwindPostcssVersion: src_TAILWIND_POSTCSS_VERSION,
|
|
@@ -6570,7 +7151,7 @@ async function main() {
|
|
|
6570
7151
|
...packageJson.modernjs ?? {},
|
|
6571
7152
|
preset: 'presetUltramodern',
|
|
6572
7153
|
packageSource: {
|
|
6573
|
-
strategy:
|
|
7154
|
+
strategy: packageSource.strategy,
|
|
6574
7155
|
config: './.modernjs/ultramodern-package-source.json'
|
|
6575
7156
|
}
|
|
6576
7157
|
};
|
|
@@ -6605,7 +7186,7 @@ async function main() {
|
|
|
6605
7186
|
}
|
|
6606
7187
|
node_fs.writeFileSync(targetPackageJson, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
6607
7188
|
writeTemplateManifestEvidence(targetDir, templateManifest);
|
|
6608
|
-
writeSingleAppPackageSourceEvidence(targetDir, packageSource
|
|
7189
|
+
writeSingleAppPackageSourceEvidence(targetDir, packageSource);
|
|
6609
7190
|
if (!isSubproject) initializeGeneratedGitRepository(targetDir);
|
|
6610
7191
|
const dim = '\x1b[2m\x1b[3m';
|
|
6611
7192
|
const reset = '\x1b[0m';
|