@bleedingdev/modern-js-create 3.2.0-ultramodern.103 → 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 +419 -127
- 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/modern.config.ts.handlebars +8 -8
- package/template/scripts/check-i18n-strings.mjs +114 -2
- package/template/scripts/validate-ultramodern.mjs.handlebars +40 -40
- package/template/tests/ultramodern.contract.test.ts.handlebars +14 -8
- package/template-workspace/README.md.handlebars +12 -3
package/README.md
CHANGED
|
@@ -42,7 +42,8 @@ domains to delete. It generates:
|
|
|
42
42
|
- `packages/shared-*` placeholders for shared contracts, tokens, and API
|
|
43
43
|
support.
|
|
44
44
|
- `.modernjs/ultramodern-generated-contract.json` with MF, Effect, i18n,
|
|
45
|
-
federated CSS, Cloudflare,
|
|
45
|
+
federated CSS, Cloudflare, route publicness, generated public-surface, and
|
|
46
|
+
Zephyr dependency metadata.
|
|
46
47
|
|
|
47
48
|
Validate the generated workspace before making application changes:
|
|
48
49
|
|
|
@@ -146,11 +147,16 @@ client, `localisedUrls`, locale JSON, CSS layer, and Cloudflare Worker output.
|
|
|
146
147
|
The shell consumes vertical UI through MF manifests and vertical APIs through
|
|
147
148
|
generated Effect clients exported by the vertical packages.
|
|
148
149
|
|
|
149
|
-
Route
|
|
150
|
+
Route metadata is route-owned. Each app writes
|
|
150
151
|
`src/routes/ultramodern-route-metadata` and passes
|
|
151
152
|
`ultramodernLocalisedUrls` into `@modern-js/plugin-i18n`. Locale JSON is served
|
|
152
153
|
from `/locales/{{lng}}/{{ns}}.json`; Czech and English routes are generated from
|
|
153
|
-
the route owner, not from shell rewrites.
|
|
154
|
+
the route owner, not from shell rewrites. Routes default to
|
|
155
|
+
`privateByDefault: true` and `publicnessDefault: private-app-screen`; generated
|
|
156
|
+
public files use only explicit `public && indexable` route metadata, so private
|
|
157
|
+
app screens publish only a disallowing `robots.txt` by default. Sitemap,
|
|
158
|
+
manifest, `llms.txt`, API catalog, JSON-LD, and broad web profile/certification
|
|
159
|
+
output stay omitted unless a safe public input exists.
|
|
154
160
|
|
|
155
161
|
CSS federation is explicit:
|
|
156
162
|
|
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';
|
|
@@ -581,7 +639,6 @@ const REACT_VERSION = '^19.2.6';
|
|
|
581
639
|
const REACT_DOM_VERSION = '^19.2.6';
|
|
582
640
|
const REACT_ROUTER_DOM_VERSION = '7.16.0';
|
|
583
641
|
const PNPM_VERSION = '11.5.0';
|
|
584
|
-
const WORKSPACE_PACKAGE_VERSION = 'workspace:*';
|
|
585
642
|
const GENERATED_CONTRACT_PATH = '.modernjs/ultramodern-generated-contract.json';
|
|
586
643
|
const RSTACK_AGENT_SKILLS_COMMIT = '61c948b42512e223bad44b83af4080eba48b2677';
|
|
587
644
|
const MODULE_FEDERATION_AGENT_SKILLS_COMMIT = '07bb5b6c43ad457609e00c081b72d4c42508ec76';
|
|
@@ -605,13 +662,6 @@ const privateAgentSkills = [
|
|
|
605
662
|
'debugger-mode'
|
|
606
663
|
];
|
|
607
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);\"";
|
|
608
|
-
const modernPackageNames = [
|
|
609
|
-
'@modern-js/app-tools',
|
|
610
|
-
'@modern-js/plugin-bff',
|
|
611
|
-
'@modern-js/plugin-i18n',
|
|
612
|
-
'@modern-js/plugin-tanstack',
|
|
613
|
-
'@modern-js/runtime'
|
|
614
|
-
];
|
|
615
665
|
function sortJsonValue(value) {
|
|
616
666
|
if (Array.isArray(value)) return value.map(sortJsonValue);
|
|
617
667
|
if (null !== value && 'object' == typeof value) return Object.fromEntries(Object.entries(value).sort(([left], [right])=>left.localeCompare(right)).map(([key, entry])=>[
|
|
@@ -620,6 +670,10 @@ function sortJsonValue(value) {
|
|
|
620
670
|
]));
|
|
621
671
|
return value;
|
|
622
672
|
}
|
|
673
|
+
const supportedWorkspaceLanguages = [
|
|
674
|
+
'en',
|
|
675
|
+
'cs'
|
|
676
|
+
];
|
|
623
677
|
const ULTRAMODERN_WORKSPACE_FLAG = '--ultramodern-workspace';
|
|
624
678
|
const FIRST_VERTICAL_PORT = 4101;
|
|
625
679
|
const TAILWIND_PREFIX_DIGIT_WORDS = [
|
|
@@ -914,28 +968,23 @@ function relativeRootFor(packageDir) {
|
|
|
914
968
|
return normalizePath(node_path.relative(packageDir, '.') || '.');
|
|
915
969
|
}
|
|
916
970
|
function resolvePackageSource(options) {
|
|
917
|
-
const strategy = options.packageSource?.strategy ?? '
|
|
918
|
-
return {
|
|
971
|
+
const strategy = options.packageSource?.strategy ?? 'install';
|
|
972
|
+
if ('workspace' === strategy) return {
|
|
919
973
|
strategy,
|
|
920
|
-
modernPackageVersion:
|
|
974
|
+
modernPackageVersion: WORKSPACE_PACKAGE_VERSION,
|
|
921
975
|
registry: options.packageSource?.registry,
|
|
922
976
|
aliasScope: options.packageSource?.aliasScope,
|
|
923
977
|
aliasPackageNamePrefix: options.packageSource?.aliasPackageNamePrefix
|
|
924
978
|
};
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
return
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
}
|
|
935
|
-
function modernPackageSpecifier(packageName, packageSource) {
|
|
936
|
-
if ('install' !== packageSource.strategy) return WORKSPACE_PACKAGE_VERSION;
|
|
937
|
-
if (!packageSource.aliasScope) return packageSource.modernPackageVersion;
|
|
938
|
-
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
|
+
};
|
|
939
988
|
}
|
|
940
989
|
function appDependencies(scope, packageSource, app, remotes = []) {
|
|
941
990
|
const dependencies = {
|
|
@@ -1420,7 +1469,19 @@ if (
|
|
|
1420
1469
|
export default defineConfig(
|
|
1421
1470
|
presetUltramodern(
|
|
1422
1471
|
{
|
|
1423
|
-
${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: {
|
|
1424
1485
|
outputStructure: 'flat',
|
|
1425
1486
|
},
|
|
1426
1487
|
output: {
|
|
@@ -1457,6 +1518,9 @@ ${bffConfig} html: {
|
|
|
1457
1518
|
'/mf-manifest.json',
|
|
1458
1519
|
'/mf-stats.json',
|
|
1459
1520
|
'/remoteEntry.js',
|
|
1521
|
+
'/robots.txt',
|
|
1522
|
+
'/site.webmanifest',
|
|
1523
|
+
'/sitemap.xml',
|
|
1460
1524
|
'/static',
|
|
1461
1525
|
'/zephyr-manifest.json',
|
|
1462
1526
|
],
|
|
@@ -1469,6 +1533,20 @@ ${bffConfig} html: {
|
|
|
1469
1533
|
${bffPluginEntry} moduleFederationPlugin(),
|
|
1470
1534
|
zephyrRspackPlugin(),
|
|
1471
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
|
+
},
|
|
1472
1550
|
tools: {
|
|
1473
1551
|
autoprefixer: {
|
|
1474
1552
|
overrideBrowserslist: ['defaults'],
|
|
@@ -1485,31 +1563,6 @@ ${bffPluginEntry} moduleFederationPlugin(),
|
|
|
1485
1563
|
]);
|
|
1486
1564
|
},
|
|
1487
1565
|
},
|
|
1488
|
-
...(cloudflareDeployEnabled
|
|
1489
|
-
? {
|
|
1490
|
-
deploy: {
|
|
1491
|
-
worker: {
|
|
1492
|
-
compatibilityDate: '${CLOUDFLARE_COMPATIBILITY_DATE}',
|
|
1493
|
-
security: ${formatTsJsonValue(createCloudflareSecurityContract(), 16)},
|
|
1494
|
-
ssr: true,
|
|
1495
|
-
},
|
|
1496
|
-
},
|
|
1497
|
-
}
|
|
1498
|
-
: {}),
|
|
1499
|
-
server: {
|
|
1500
|
-
port,
|
|
1501
|
-
publicDir: ['./locales', './assets'],
|
|
1502
|
-
ssr: {
|
|
1503
|
-
mode: 'string',
|
|
1504
|
-
moduleFederationAppSSR: true,
|
|
1505
|
-
},
|
|
1506
|
-
},
|
|
1507
|
-
source: {
|
|
1508
|
-
globalVars: {
|
|
1509
|
-
ULTRAMODERN_SITE_URL: siteUrl,
|
|
1510
|
-
},
|
|
1511
|
-
mainEntryName: 'index',
|
|
1512
|
-
},
|
|
1513
1566
|
},
|
|
1514
1567
|
{
|
|
1515
1568
|
appId,
|
|
@@ -2128,15 +2181,125 @@ function createPostcssConfig() {
|
|
|
2128
2181
|
function createTailwindConfig() {
|
|
2129
2182
|
return `import type { Config } from 'tailwindcss';
|
|
2130
2183
|
|
|
2131
|
-
export default {
|
|
2132
|
-
} satisfies Config;
|
|
2184
|
+
export default {} satisfies Config;
|
|
2133
2185
|
`;
|
|
2134
2186
|
}
|
|
2135
2187
|
function createTw(prefix) {
|
|
2136
2188
|
return (classList)=>classList.split(/\s+/u).filter(Boolean).map((candidate)=>`${prefix}:${candidate.replace(/\[&&\]:/gu, '')}`).join(' ');
|
|
2137
2189
|
}
|
|
2138
|
-
|
|
2139
|
-
|
|
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);
|
|
2140
2303
|
}
|
|
2141
2304
|
function createLocalizedHeadComponent() {
|
|
2142
2305
|
return `const fallbackLanguage = 'en';
|
|
@@ -4086,19 +4249,10 @@ function createDevelopmentOverlay(remotes = []) {
|
|
|
4086
4249
|
};
|
|
4087
4250
|
}
|
|
4088
4251
|
function createPackageSourceMetadata(scope, packageSource) {
|
|
4089
|
-
const modernPackages = {
|
|
4090
|
-
packages: modernPackageNames,
|
|
4091
|
-
specifier: modernPackageVersion(packageSource)
|
|
4092
|
-
};
|
|
4093
|
-
if (packageSource.registry) modernPackages.registry = packageSource.registry;
|
|
4094
|
-
if (packageSource.aliasScope) modernPackages.aliases = Object.fromEntries(modernPackageNames.map((packageName)=>[
|
|
4095
|
-
packageName,
|
|
4096
|
-
modernAliasPackageName(packageName, packageSource)
|
|
4097
|
-
]));
|
|
4098
4252
|
return {
|
|
4099
4253
|
schemaVersion: 1,
|
|
4100
4254
|
strategy: packageSource.strategy,
|
|
4101
|
-
modernPackages,
|
|
4255
|
+
modernPackages: createModernPackagesMetadata(ULTRAMODERN_WORKSPACE_MODERN_PACKAGES, packageSource),
|
|
4102
4256
|
generatedWorkspacePackages: {
|
|
4103
4257
|
packages: sharedPackages.map((sharedPackage)=>ultramodern_workspace_packageName(scope, sharedPackage.id)),
|
|
4104
4258
|
specifier: WORKSPACE_PACKAGE_VERSION
|
|
@@ -4348,6 +4502,23 @@ function createStylingContract(scope, app, enableTailwind) {
|
|
|
4348
4502
|
federation: createAppCssFederationContract(scope, app)
|
|
4349
4503
|
};
|
|
4350
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
|
+
}
|
|
4351
4522
|
function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
4352
4523
|
const appWithResolvedRefs = 'shell' === app.kind ? {
|
|
4353
4524
|
...app,
|
|
@@ -4416,7 +4587,8 @@ function createAppGeneratedContract(scope, app, apps, enableTailwind) {
|
|
|
4416
4587
|
privateByDefault: true,
|
|
4417
4588
|
publicnessDefault: 'private-app-screen',
|
|
4418
4589
|
generatedRouteMap: true,
|
|
4419
|
-
manualOverrides: []
|
|
4590
|
+
manualOverrides: [],
|
|
4591
|
+
publicSurface: createPublicSurfaceContract(app)
|
|
4420
4592
|
},
|
|
4421
4593
|
moduleFederation: {
|
|
4422
4594
|
name: app.mfName,
|
|
@@ -4908,6 +5080,21 @@ const expectedBuildScript = ${JSON.stringify(expectedBuildScript)};
|
|
|
4908
5080
|
const expectedCloudflareBuildScript = ${JSON.stringify(expectedCloudflareBuildScript)};
|
|
4909
5081
|
const expectedCloudflareDeployScript = ${JSON.stringify(expectedCloudflareDeployScript)};
|
|
4910
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
|
+
};
|
|
4911
5098
|
|
|
4912
5099
|
const readText = relativePath => fs.readFileSync(path.join(root, relativePath), 'utf-8');
|
|
4913
5100
|
const readJson = relativePath => JSON.parse(readText(relativePath));
|
|
@@ -4922,6 +5109,21 @@ const assertExists = relativePath => {
|
|
|
4922
5109
|
const assertNotExists = relativePath => {
|
|
4923
5110
|
assert(!fs.existsSync(path.join(root, relativePath)), \`Unexpected \${relativePath}\`);
|
|
4924
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
|
+
};
|
|
4925
5127
|
const expectedWorkerName = packageSuffix => \`\${packageScope}-\${packageSuffix}\`.slice(0, 63);
|
|
4926
5128
|
const expectedChunkLoadingGlobal = mfName =>
|
|
4927
5129
|
\`__ULTRAMODERN_\${mfName
|
|
@@ -4998,6 +5200,9 @@ const requiredPaths = [
|
|
|
4998
5200
|
'apps/shell-super-app/src/routes/layout.tsx',
|
|
4999
5201
|
'apps/shell-super-app/src/routes/ultramodern-route-metadata.ts',
|
|
5000
5202
|
'apps/shell-super-app/src/routes/[lang]/page.tsx',
|
|
5203
|
+
...publicSurfaceRequiredAssetPaths.map(
|
|
5204
|
+
relativePath => \`apps/shell-super-app/\${relativePath}\`,
|
|
5205
|
+
),
|
|
5001
5206
|
'packages/shared-contracts/src/index.ts',
|
|
5002
5207
|
'packages/shared-design-tokens/src/index.ts',
|
|
5003
5208
|
'packages/shared-design-tokens/src/tokens.css',
|
|
@@ -5024,6 +5229,9 @@ for (const vertical of fullStackVerticals) {
|
|
|
5024
5229
|
\`\${vertical.path}/src/routes/layout.tsx\`,
|
|
5025
5230
|
\`\${vertical.path}/src/routes/ultramodern-route-metadata.ts\`,
|
|
5026
5231
|
\`\${vertical.path}/src/routes/[lang]/page.tsx\`,
|
|
5232
|
+
...publicSurfaceRequiredAssetPaths.map(
|
|
5233
|
+
relativePath => \`\${vertical.path}/\${relativePath}\`,
|
|
5234
|
+
),
|
|
5027
5235
|
...vertical.routePagePaths,
|
|
5028
5236
|
);
|
|
5029
5237
|
}
|
|
@@ -5058,6 +5266,31 @@ assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare
|
|
|
5058
5266
|
assert(rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json', 'Root must point at package source metadata');
|
|
5059
5267
|
assert(rootPackage.modernjs?.packageSource?.strategy === packageSource.strategy, 'Root package source strategy must match metadata');
|
|
5060
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
|
+
}
|
|
5061
5294
|
assert(packageSource.generatedWorkspacePackages?.specifier === 'workspace:*', 'Generated workspace packages must keep workspace:* links');
|
|
5062
5295
|
assert(
|
|
5063
5296
|
rootPackage.scripts?.build === expectedBuildScript,
|
|
@@ -5091,6 +5324,7 @@ assert(generatedContract.cssFederation?.sharedDesignTokens?.dedupe?.duplicateBas
|
|
|
5091
5324
|
assert(generatedContract.cssFederation?.sharedDesignTokens?.ssr?.firstPaintRequired === true, 'Shared design token CSS must be required for SSR first paint');
|
|
5092
5325
|
|
|
5093
5326
|
const shellPackage = readJson('apps/shell-super-app/package.json');
|
|
5327
|
+
const shellModernConfig = readText('apps/shell-super-app/modern.config.ts');
|
|
5094
5328
|
const expectedZephyrDependencies = Object.fromEntries(
|
|
5095
5329
|
fullStackVerticals.map(vertical => [
|
|
5096
5330
|
vertical.zephyrAlias,
|
|
@@ -5102,6 +5336,11 @@ assert(
|
|
|
5102
5336
|
JSON.stringify(expectedZephyrDependencies),
|
|
5103
5337
|
'Shell Zephyr dependencies must reference every vertical package',
|
|
5104
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');
|
|
5105
5344
|
const shellContract = generatedContract.apps?.find(app => app.id === 'shell-super-app');
|
|
5106
5345
|
assert(shellContract?.deploy?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell Cloudflare workerName is incorrect');
|
|
5107
5346
|
assert(shellContract?.deploy?.cloudflare?.publicUrlEnv === 'ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP', 'Shell Cloudflare public URL env is incorrect');
|
|
@@ -5109,6 +5348,9 @@ assert(shellContract?.deploy?.cloudflare?.compatibilityDate === expectedCloudfla
|
|
|
5109
5348
|
assert(JSON.stringify(shellContract?.deploy?.cloudflare?.compatibilityFlags) === JSON.stringify(expectedCloudflareCompatibilityFlags), 'Shell Cloudflare compatibility flags are incorrect');
|
|
5110
5349
|
assert(JSON.stringify(shellContract?.deploy?.cloudflare?.security) === JSON.stringify(expectedCloudflareSecurity), 'Shell Cloudflare security contract is incorrect');
|
|
5111
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');
|
|
5112
5354
|
assert(shellContract?.config?.rspack?.output?.uniqueName === 'shellSuperApp', 'Shell Rspack uniqueName is incorrect');
|
|
5113
5355
|
assert(shellContract?.config?.rspack?.output?.chunkLoadingGlobal === expectedChunkLoadingGlobal('shellSuperApp'), 'Shell Rspack chunkLoadingGlobal is incorrect');
|
|
5114
5356
|
assert(topology.shell?.cloudflare?.workerName === expectedWorkerName('shell-super-app'), 'Shell topology Cloudflare workerName is incorrect');
|
|
@@ -5129,6 +5371,7 @@ assert(
|
|
|
5129
5371
|
(shellContract?.routes?.owned ?? []).every(route => route.public === false && route.indexable === false && route.publicSurface === 'private-app-screen'),
|
|
5130
5372
|
'Shell owned routes must be non-indexable private app screens by default',
|
|
5131
5373
|
);
|
|
5374
|
+
assertPublicSurfaceAssets('apps/shell-super-app', shellContract?.routes?.publicRoutes ?? []);
|
|
5132
5375
|
assert(
|
|
5133
5376
|
topology.shell?.verticalRefs?.join(',') === fullStackVerticals.map(vertical => vertical.id).join(','),
|
|
5134
5377
|
'Topology shell verticalRefs must match generated verticals',
|
|
@@ -5139,10 +5382,15 @@ assert(!('effectServices' in topology), 'Default APIs must be vertical-owned, no
|
|
|
5139
5382
|
|
|
5140
5383
|
for (const vertical of fullStackVerticals) {
|
|
5141
5384
|
const packageJson = readJson(\`\${vertical.path}/package.json\`);
|
|
5385
|
+
const modernConfig = readText(\`\${vertical.path}/modern.config.ts\`);
|
|
5142
5386
|
assert(packageJson.name === vertical.packageName, \`\${vertical.id} package name is incorrect\`);
|
|
5143
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\`);
|
|
5144
5388
|
assert(packageJson.scripts?.['cloudflare:proof']?.includes(\`--app \${vertical.id}\`), \`\${vertical.id} must expose cloudflare:proof\`);
|
|
5145
|
-
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\`);
|
|
5146
5394
|
assert(packageJson.exports?.['./effect/client'] === \`./src/effect/\${vertical.stem}-client.ts\`, \`\${vertical.id} must export its Effect client\`);
|
|
5147
5395
|
assert(packageJson.exports?.['./shared/effect/api'] === './shared/effect/api.ts', \`\${vertical.id} must export its Effect API contract\`);
|
|
5148
5396
|
const expectedVerticalZephyrDependencies = Object.fromEntries(
|
|
@@ -5168,6 +5416,9 @@ for (const vertical of fullStackVerticals) {
|
|
|
5168
5416
|
assert(JSON.stringify(contractEntry?.deploy?.cloudflare?.compatibilityFlags) === JSON.stringify(expectedCloudflareCompatibilityFlags), \`\${vertical.id} Cloudflare compatibility flags are incorrect\`);
|
|
5169
5417
|
assert(JSON.stringify(contractEntry?.deploy?.cloudflare?.security) === JSON.stringify(expectedCloudflareSecurity), \`\${vertical.id} Cloudflare security contract is incorrect\`);
|
|
5170
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\`);
|
|
5171
5422
|
assert(contractEntry?.deploy?.cloudflare?.routes?.effectReadiness === \`\${vertical.apiPrefix}/effect/\${vertical.stem}/readiness\`, \`\${vertical.id} Cloudflare proof readiness route is incorrect\`);
|
|
5172
5423
|
assert(contractEntry?.config?.rspack?.output?.uniqueName === vertical.mfName, \`\${vertical.id} Rspack uniqueName is incorrect\`);
|
|
5173
5424
|
assert(contractEntry?.config?.rspack?.output?.chunkLoadingGlobal === expectedChunkLoadingGlobal(vertical.mfName), \`\${vertical.id} Rspack chunkLoadingGlobal is incorrect\`);
|
|
@@ -5201,6 +5452,7 @@ for (const vertical of fullStackVerticals) {
|
|
|
5201
5452
|
(contractEntry?.routes?.owned ?? []).every(route => route.public === false && route.indexable === false && route.publicSurface === 'private-app-screen'),
|
|
5202
5453
|
\`\${vertical.id} owned routes must be non-indexable private app screens by default\`,
|
|
5203
5454
|
);
|
|
5455
|
+
assertPublicSurfaceAssets(vertical.path, contractEntry?.routes?.publicRoutes ?? []);
|
|
5204
5456
|
assert(contractEntry?.styling?.federation?.owner?.id === vertical.id, \`\${vertical.id} CSS federation owner is missing\`);
|
|
5205
5457
|
assert(contractEntry?.styling?.federation?.role === 'vertical-css', \`\${vertical.id} must own only vertical CSS\`);
|
|
5206
5458
|
assert(contractEntry?.styling?.federation?.rootSelector === \`[data-app-id="\${vertical.id}"]\`, \`\${vertical.id} CSS root selector is incorrect\`);
|
|
@@ -5859,6 +6111,7 @@ function rewriteShellAppFiles(workspaceRoot, scope, packageSource, enableTailwin
|
|
|
5859
6111
|
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/package.json`), createAppPackage(scope, shellHost, packageSource, enableTailwind, remotes));
|
|
5860
6112
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern-app-env.d.ts`, createAppEnvDts(shellHost, remotes));
|
|
5861
6113
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/routes/ultramodern-route-metadata.ts`, createRouteMetadataModule(shellHost));
|
|
6114
|
+
rewriteWorkspaceAssetsForApp(workspaceRoot, shellHost);
|
|
5862
6115
|
writeFileReplacing(workspaceRoot, `${shellApp.directory}/src/modern.runtime.ts`, createAppRuntimeConfig(shellHost, scope, remotes));
|
|
5863
6116
|
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/translation.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
|
|
5864
6117
|
writeJsonFile(node_path.join(workspaceRoot, `${shellApp.directory}/locales/en/${appI18nNamespace(shellHost)}.json`), createAppPublicLocaleMessages(shellHost, 'en', remotes));
|
|
@@ -6427,7 +6680,7 @@ function readCreatePackageJson() {
|
|
|
6427
6680
|
return JSON.parse(node_fs.readFileSync(createPackageJson, 'utf-8'));
|
|
6428
6681
|
}
|
|
6429
6682
|
function isBleedingDevCreatePackage(createPackage) {
|
|
6430
|
-
return
|
|
6683
|
+
return createPackage.name === BLEEDINGDEV_CREATE_PACKAGE;
|
|
6431
6684
|
}
|
|
6432
6685
|
function getBleedingDevFrameworkVersion(createPackage, fallbackVersion) {
|
|
6433
6686
|
const frameworkVersion = createPackage.ultramodern?.frameworkVersion;
|
|
@@ -6534,71 +6787,109 @@ function detectUltramodernPackageSource(args, defaultPackageVersion, createPacka
|
|
|
6534
6787
|
console.error('--ultramodern-package-source must be "workspace" or "install"');
|
|
6535
6788
|
process.exit(1);
|
|
6536
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);
|
|
6537
6797
|
return {
|
|
6538
|
-
strategy,
|
|
6798
|
+
strategy: packageSourceStrategy,
|
|
6539
6799
|
modernPackageVersion: getOptionValue(args, [
|
|
6540
6800
|
'--ultramodern-package-version'
|
|
6541
6801
|
]) ?? defaultPackageVersion,
|
|
6542
|
-
registry:
|
|
6543
|
-
|
|
6544
|
-
]),
|
|
6545
|
-
aliasScope: getOptionValue(args, [
|
|
6546
|
-
'--ultramodern-package-scope'
|
|
6547
|
-
]) ?? (bleedingDevDefaults && 'install' === strategy ? 'bleedingdev' : void 0),
|
|
6802
|
+
registry: explicitRegistry,
|
|
6803
|
+
aliasScope,
|
|
6548
6804
|
aliasPackageNamePrefix: getOptionValue(args, [
|
|
6549
6805
|
'--ultramodern-package-name-prefix'
|
|
6550
|
-
]) ??
|
|
6806
|
+
]) ?? (aliasScope ? BLEEDINGDEV_PACKAGE_NAME_PREFIX : void 0)
|
|
6551
6807
|
};
|
|
6552
6808
|
}
|
|
6553
|
-
function
|
|
6554
|
-
|
|
6555
|
-
|
|
6556
|
-
|
|
6557
|
-
return
|
|
6809
|
+
function hasExplicitUltramodernPackageSource(args, value) {
|
|
6810
|
+
const configuredValue = getOptionValue(args, [
|
|
6811
|
+
'--ultramodern-package-source'
|
|
6812
|
+
]);
|
|
6813
|
+
return value ? configuredValue === value : void 0 !== configuredValue;
|
|
6558
6814
|
}
|
|
6559
|
-
function
|
|
6560
|
-
|
|
6561
|
-
if (
|
|
6562
|
-
|
|
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);
|
|
6563
6840
|
}
|
|
6564
|
-
|
|
6565
|
-
|
|
6566
|
-
|
|
6567
|
-
|
|
6568
|
-
|
|
6569
|
-
|
|
6570
|
-
|
|
6571
|
-
|
|
6572
|
-
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
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) {
|
|
6580
6878
|
return {
|
|
6581
6879
|
schemaVersion: 1,
|
|
6582
6880
|
preset: 'presetUltramodern',
|
|
6583
|
-
strategy,
|
|
6584
|
-
modernPackages: {
|
|
6585
|
-
|
|
6586
|
-
|
|
6587
|
-
...packageSource.registry ? {
|
|
6588
|
-
registry: packageSource.registry
|
|
6589
|
-
} : {},
|
|
6590
|
-
...aliases ? {
|
|
6591
|
-
aliases
|
|
6592
|
-
} : {}
|
|
6593
|
-
}
|
|
6881
|
+
strategy: packageSource.strategy,
|
|
6882
|
+
modernPackages: createModernPackagesMetadata(ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES, packageSource, {
|
|
6883
|
+
includeAliases: 'install' === packageSource.strategy
|
|
6884
|
+
})
|
|
6594
6885
|
};
|
|
6595
6886
|
}
|
|
6596
|
-
function writeSingleAppPackageSourceEvidence(targetDir, packageSource
|
|
6887
|
+
function writeSingleAppPackageSourceEvidence(targetDir, packageSource) {
|
|
6597
6888
|
const evidencePath = node_path.join(targetDir, '.modernjs', 'ultramodern-package-source.json');
|
|
6598
6889
|
node_fs.mkdirSync(node_path.dirname(evidencePath), {
|
|
6599
6890
|
recursive: true
|
|
6600
6891
|
});
|
|
6601
|
-
node_fs.writeFileSync(evidencePath, `${JSON.stringify(createSingleAppPackageSourceEvidence(packageSource
|
|
6892
|
+
node_fs.writeFileSync(evidencePath, `${JSON.stringify(createSingleAppPackageSourceEvidence(packageSource), null, 2)}\n`);
|
|
6602
6893
|
}
|
|
6603
6894
|
function runSetupCommand(command, args, options = {}) {
|
|
6604
6895
|
return execFileSync(command, args, {
|
|
@@ -6803,12 +7094,13 @@ async function main() {
|
|
|
6803
7094
|
}
|
|
6804
7095
|
const generateWorkspace = detectUltramodernWorkspaceFlag();
|
|
6805
7096
|
if (generateWorkspace) {
|
|
7097
|
+
const packageSource = resolveWorkspacePackageSource(args, createPackage, detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage));
|
|
6806
7098
|
generateUltramodernWorkspace({
|
|
6807
7099
|
targetDir,
|
|
6808
7100
|
packageName: generatedPackageName,
|
|
6809
7101
|
modernVersion: version,
|
|
6810
7102
|
enableTailwind: detectTailwindFlag(),
|
|
6811
|
-
packageSource
|
|
7103
|
+
packageSource
|
|
6812
7104
|
});
|
|
6813
7105
|
initializeGeneratedGitRepository(targetDir);
|
|
6814
7106
|
const dim = '\x1b[2m\x1b[3m';
|
|
@@ -6829,19 +7121,19 @@ async function main() {
|
|
|
6829
7121
|
const bffRuntime = detectBffRuntime();
|
|
6830
7122
|
const enableTailwind = detectTailwindFlag();
|
|
6831
7123
|
const useWorkspaceProtocol = detectWorkspaceProtocolFlag();
|
|
6832
|
-
const packageSource = detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage);
|
|
6833
|
-
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);
|
|
6834
7126
|
validateTemplateManifest(templateManifest);
|
|
6835
7127
|
copyTemplate(templateDir, targetDir, {
|
|
6836
7128
|
packageName: generatedPackageName,
|
|
6837
|
-
version:
|
|
6838
|
-
runtimeVersion:
|
|
6839
|
-
appToolsVersion:
|
|
6840
|
-
adapterRstestVersion:
|
|
6841
|
-
tsconfigVersion:
|
|
6842
|
-
pluginTanstackVersion:
|
|
6843
|
-
pluginBffVersion:
|
|
6844
|
-
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),
|
|
6845
7137
|
tanstackRouterVersion: src_TANSTACK_ROUTER_VERSION,
|
|
6846
7138
|
tailwindVersion: src_TAILWIND_VERSION,
|
|
6847
7139
|
tailwindPostcssVersion: src_TAILWIND_POSTCSS_VERSION,
|
|
@@ -6859,7 +7151,7 @@ async function main() {
|
|
|
6859
7151
|
...packageJson.modernjs ?? {},
|
|
6860
7152
|
preset: 'presetUltramodern',
|
|
6861
7153
|
packageSource: {
|
|
6862
|
-
strategy:
|
|
7154
|
+
strategy: packageSource.strategy,
|
|
6863
7155
|
config: './.modernjs/ultramodern-package-source.json'
|
|
6864
7156
|
}
|
|
6865
7157
|
};
|
|
@@ -6894,7 +7186,7 @@ async function main() {
|
|
|
6894
7186
|
}
|
|
6895
7187
|
node_fs.writeFileSync(targetPackageJson, `${JSON.stringify(packageJson, null, 2)}\n`);
|
|
6896
7188
|
writeTemplateManifestEvidence(targetDir, templateManifest);
|
|
6897
|
-
writeSingleAppPackageSourceEvidence(targetDir, packageSource
|
|
7189
|
+
writeSingleAppPackageSourceEvidence(targetDir, packageSource);
|
|
6898
7190
|
if (!isSubproject) initializeGeneratedGitRepository(targetDir);
|
|
6899
7191
|
const dim = '\x1b[2m\x1b[3m';
|
|
6900
7192
|
const reset = '\x1b[0m';
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const WORKSPACE_PACKAGE_VERSION = "workspace:*";
|
|
2
|
+
export declare const BLEEDINGDEV_CREATE_PACKAGE = "@bleedingdev/modern-js-create";
|
|
3
|
+
export declare const BLEEDINGDEV_PACKAGE_SCOPE = "bleedingdev";
|
|
4
|
+
export declare const BLEEDINGDEV_PACKAGE_NAME_PREFIX = "modern-js-";
|
|
5
|
+
export declare const BLEEDINGDEV_FRAMEWORK_VERSION_ENV = "MODERN_CREATE_ULTRAMODERN_FRAMEWORK_VERSION";
|
|
6
|
+
export declare const ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES: readonly ['@modern-js/runtime', '@modern-js/app-tools', '@modern-js/tsconfig', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/plugin-bff', '@modern-js/adapter-rstest'];
|
|
7
|
+
export declare const ULTRAMODERN_WORKSPACE_MODERN_PACKAGES: readonly ['@modern-js/app-tools', '@modern-js/plugin-bff', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/runtime'];
|
|
8
|
+
export type UltramodernPackageSourceStrategy = 'workspace' | 'install';
|
|
9
|
+
export type ResolvedUltramodernPackageSource = {
|
|
10
|
+
strategy: UltramodernPackageSourceStrategy;
|
|
11
|
+
modernPackageVersion: string;
|
|
12
|
+
registry?: string;
|
|
13
|
+
aliasScope?: string;
|
|
14
|
+
aliasPackageNamePrefix?: string;
|
|
15
|
+
};
|
|
16
|
+
export type UltramodernModernPackagesMetadata = {
|
|
17
|
+
packages: string[];
|
|
18
|
+
specifier: string;
|
|
19
|
+
registry?: string;
|
|
20
|
+
aliases?: Record<string, string>;
|
|
21
|
+
};
|
|
22
|
+
export declare function modernPackageVersion(packageSource: ResolvedUltramodernPackageSource): string;
|
|
23
|
+
export declare function modernAliasPackageName(packageName: string, packageSource: ResolvedUltramodernPackageSource): string;
|
|
24
|
+
export declare function modernPackageSpecifier(packageName: string, packageSource: ResolvedUltramodernPackageSource): string;
|
|
25
|
+
export declare function modernPackageAliases(packageNames: readonly string[], packageSource: ResolvedUltramodernPackageSource): Record<string, string> | undefined;
|
|
26
|
+
export declare function createModernPackagesMetadata(packageNames: readonly string[], packageSource: ResolvedUltramodernPackageSource, options?: {
|
|
27
|
+
includeAliases?: boolean;
|
|
28
|
+
}): UltramodernModernPackagesMetadata;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type UltramodernPackageSourceStrategy
|
|
1
|
+
import { type UltramodernPackageSourceStrategy } from './ultramodern-package-source';
|
|
2
2
|
export type UltramodernWorkspaceOptions = {
|
|
3
3
|
targetDir: string;
|
|
4
4
|
packageName: string;
|
|
@@ -28,4 +28,3 @@ export declare const ultramodernWorkspaceVersions: {
|
|
|
28
28
|
tailwind: string;
|
|
29
29
|
tailwindPostcss: string;
|
|
30
30
|
};
|
|
31
|
-
export {};
|
package/package.json
CHANGED
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
"engines": {
|
|
22
22
|
"node": ">=20"
|
|
23
23
|
},
|
|
24
|
-
"version": "3.2.0-ultramodern.
|
|
24
|
+
"version": "3.2.0-ultramodern.104",
|
|
25
25
|
"types": "./dist/types/index.d.ts",
|
|
26
26
|
"main": "./dist/index.js",
|
|
27
27
|
"bin": {
|
|
@@ -41,7 +41,7 @@
|
|
|
41
41
|
"@types/node": "^25.9.1",
|
|
42
42
|
"@typescript/native-preview": "7.0.0-dev.20260527.2",
|
|
43
43
|
"tsx": "^4.22.3",
|
|
44
|
-
"@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.
|
|
44
|
+
"@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.104"
|
|
45
45
|
},
|
|
46
46
|
"publishConfig": {
|
|
47
47
|
"registry": "https://registry.npmjs.org/",
|
|
@@ -54,6 +54,6 @@
|
|
|
54
54
|
"start": "node ./dist/index.js"
|
|
55
55
|
},
|
|
56
56
|
"ultramodern": {
|
|
57
|
-
"frameworkVersion": "3.2.0-ultramodern.
|
|
57
|
+
"frameworkVersion": "3.2.0-ultramodern.104"
|
|
58
58
|
}
|
|
59
59
|
}
|
|
@@ -39,7 +39,13 @@ export default defineConfig(
|
|
|
39
39
|
{{/if}} runtimeFramework: '{{bffRuntime}}',
|
|
40
40
|
},
|
|
41
41
|
|
|
42
|
-
{{/if}}
|
|
42
|
+
{{/if}} html: {
|
|
43
|
+
meta: {
|
|
44
|
+
viewport: 'width=device-width, initial-scale=1.0, viewport-fit=cover',
|
|
45
|
+
},
|
|
46
|
+
title: 'UltraModern.js Starter',
|
|
47
|
+
},
|
|
48
|
+
plugins: [
|
|
43
49
|
appTools(),
|
|
44
50
|
i18nPlugin({
|
|
45
51
|
localeDetection: {
|
|
@@ -53,12 +59,6 @@ export default defineConfig(
|
|
|
53
59
|
{{/if}}{{#if enableBff}}
|
|
54
60
|
bffPlugin(),
|
|
55
61
|
{{/if}} ],
|
|
56
|
-
html: {
|
|
57
|
-
title: 'UltraModern.js Starter',
|
|
58
|
-
meta: {
|
|
59
|
-
viewport: 'width=device-width, initial-scale=1.0, viewport-fit=cover',
|
|
60
|
-
},
|
|
61
|
-
},
|
|
62
62
|
source: {
|
|
63
63
|
globalVars: {
|
|
64
64
|
ULTRAMODERN_SITE_URL: siteUrl,
|
|
@@ -70,8 +70,8 @@ export default defineConfig(
|
|
|
70
70
|
enableBffRequestId,
|
|
71
71
|
enableModuleFederationSSR,
|
|
72
72
|
enableTelemetryExporters,
|
|
73
|
-
telemetryFailLoudStartup,
|
|
74
73
|
...(typeof otlpEndpoint === 'string' ? { otlpEndpoint } : {}),
|
|
74
|
+
telemetryFailLoudStartup,
|
|
75
75
|
...(typeof victoriaMetricsEndpoint === 'string' ? { victoriaMetricsEndpoint } : {}),
|
|
76
76
|
},
|
|
77
77
|
),
|
|
@@ -7,6 +7,97 @@ const ignoredDirectories = new Set(['.modern', '.modernjs', 'dist', 'node_module
|
|
|
7
7
|
const visibleAttributePattern =
|
|
8
8
|
/\s(?:aria-label|alt|placeholder|title)=["']([^"']*[A-Za-z][^"']*)["']/gu;
|
|
9
9
|
const jsxTextPattern = />([^<>{}]*[A-Za-z][^<>{}]*)</gu;
|
|
10
|
+
const jsxIntrinsicTags = new Set([
|
|
11
|
+
'a',
|
|
12
|
+
'abbr',
|
|
13
|
+
'address',
|
|
14
|
+
'area',
|
|
15
|
+
'article',
|
|
16
|
+
'aside',
|
|
17
|
+
'audio',
|
|
18
|
+
'b',
|
|
19
|
+
'blockquote',
|
|
20
|
+
'body',
|
|
21
|
+
'br',
|
|
22
|
+
'button',
|
|
23
|
+
'canvas',
|
|
24
|
+
'caption',
|
|
25
|
+
'cite',
|
|
26
|
+
'code',
|
|
27
|
+
'col',
|
|
28
|
+
'colgroup',
|
|
29
|
+
'data',
|
|
30
|
+
'datalist',
|
|
31
|
+
'dd',
|
|
32
|
+
'del',
|
|
33
|
+
'details',
|
|
34
|
+
'dfn',
|
|
35
|
+
'dialog',
|
|
36
|
+
'div',
|
|
37
|
+
'dl',
|
|
38
|
+
'dt',
|
|
39
|
+
'em',
|
|
40
|
+
'fieldset',
|
|
41
|
+
'figcaption',
|
|
42
|
+
'figure',
|
|
43
|
+
'footer',
|
|
44
|
+
'form',
|
|
45
|
+
'h1',
|
|
46
|
+
'h2',
|
|
47
|
+
'h3',
|
|
48
|
+
'h4',
|
|
49
|
+
'h5',
|
|
50
|
+
'h6',
|
|
51
|
+
'head',
|
|
52
|
+
'header',
|
|
53
|
+
'hr',
|
|
54
|
+
'html',
|
|
55
|
+
'i',
|
|
56
|
+
'iframe',
|
|
57
|
+
'img',
|
|
58
|
+
'input',
|
|
59
|
+
'label',
|
|
60
|
+
'legend',
|
|
61
|
+
'li',
|
|
62
|
+
'link',
|
|
63
|
+
'main',
|
|
64
|
+
'mark',
|
|
65
|
+
'menu',
|
|
66
|
+
'meta',
|
|
67
|
+
'meter',
|
|
68
|
+
'nav',
|
|
69
|
+
'ol',
|
|
70
|
+
'option',
|
|
71
|
+
'p',
|
|
72
|
+
'picture',
|
|
73
|
+
'pre',
|
|
74
|
+
'progress',
|
|
75
|
+
'q',
|
|
76
|
+
'script',
|
|
77
|
+
'section',
|
|
78
|
+
'select',
|
|
79
|
+
'small',
|
|
80
|
+
'source',
|
|
81
|
+
'span',
|
|
82
|
+
'strong',
|
|
83
|
+
'style',
|
|
84
|
+
'summary',
|
|
85
|
+
'svg',
|
|
86
|
+
'table',
|
|
87
|
+
'tbody',
|
|
88
|
+
'td',
|
|
89
|
+
'template',
|
|
90
|
+
'textarea',
|
|
91
|
+
'tfoot',
|
|
92
|
+
'th',
|
|
93
|
+
'thead',
|
|
94
|
+
'time',
|
|
95
|
+
'title',
|
|
96
|
+
'tr',
|
|
97
|
+
'u',
|
|
98
|
+
'ul',
|
|
99
|
+
'video',
|
|
100
|
+
]);
|
|
10
101
|
|
|
11
102
|
const collectFiles = (directory) => {
|
|
12
103
|
if (!fs.existsSync(directory)) {
|
|
@@ -37,6 +128,24 @@ const isCodeElementText = (content, index) => {
|
|
|
37
128
|
}
|
|
38
129
|
return /^<code(?:\s|>)/u.test(content.slice(tagStart, index));
|
|
39
130
|
};
|
|
131
|
+
const isJsxTagEnd = (content, index) => {
|
|
132
|
+
const tagStart = content.lastIndexOf('<', index);
|
|
133
|
+
if (tagStart === -1 || content.slice(tagStart + 1, index).includes('<')) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const match = content
|
|
137
|
+
.slice(tagStart, index + 1)
|
|
138
|
+
.match(/^<\/?\s*([A-Za-z][\w:.-]*)\b[^<>]*>$/u);
|
|
139
|
+
if (!match) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const [, tagName] = match;
|
|
143
|
+
return (
|
|
144
|
+
/^[A-Z]/u.test(tagName) ||
|
|
145
|
+
tagName.includes('-') ||
|
|
146
|
+
jsxIntrinsicTags.has(tagName)
|
|
147
|
+
);
|
|
148
|
+
};
|
|
40
149
|
const isIgnoredLine = (content, index) => {
|
|
41
150
|
const lineStart = content.lastIndexOf('\n', index) + 1;
|
|
42
151
|
const lineEnd = content.indexOf('\n', index);
|
|
@@ -54,20 +163,23 @@ const violations = [];
|
|
|
54
163
|
for (const filePath of scanRoots.flatMap(collectFiles)) {
|
|
55
164
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
56
165
|
for (const match of content.matchAll(visibleAttributePattern)) {
|
|
166
|
+
const [, visibleText] = match;
|
|
57
167
|
if (!isIgnoredLine(content, match.index ?? 0)) {
|
|
58
168
|
violations.push({
|
|
59
169
|
filePath,
|
|
60
170
|
line: lineNumberForIndex(content, match.index ?? 0),
|
|
61
|
-
text:
|
|
171
|
+
text: visibleText.trim(),
|
|
62
172
|
});
|
|
63
173
|
}
|
|
64
174
|
}
|
|
65
175
|
|
|
66
176
|
for (const match of content.matchAll(jsxTextPattern)) {
|
|
67
|
-
const
|
|
177
|
+
const [, jsxText] = match;
|
|
178
|
+
const text = jsxText.replaceAll(/\s+/gu, ' ').trim();
|
|
68
179
|
if (
|
|
69
180
|
text &&
|
|
70
181
|
!isIgnoredLine(content, match.index ?? 0) &&
|
|
182
|
+
isJsxTagEnd(content, match.index ?? 0) &&
|
|
71
183
|
!isCodeElementText(content, match.index ?? 0)
|
|
72
184
|
) {
|
|
73
185
|
violations.push({
|
|
@@ -481,6 +481,11 @@ if (
|
|
|
481
481
|
process.exit(1);
|
|
482
482
|
}
|
|
483
483
|
|
|
484
|
+
if (packageJson.modernjs?.packageSource?.strategy !== packageSource.strategy) {
|
|
485
|
+
console.error('package.json package source strategy must match package source metadata');
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
|
|
484
489
|
if (packageSource.schemaVersion !== 1) {
|
|
485
490
|
console.error('Package source metadata must use schemaVersion 1');
|
|
486
491
|
process.exit(1);
|
|
@@ -496,21 +501,33 @@ if (packageSource.strategy !== 'workspace' && packageSource.strategy !== 'instal
|
|
|
496
501
|
process.exit(1);
|
|
497
502
|
}
|
|
498
503
|
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
'
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
'@modern-js/plugin-tanstack',
|
|
505
|
-
'@modern-js/plugin-bff',
|
|
506
|
-
'@modern-js/adapter-rstest',
|
|
507
|
-
];
|
|
504
|
+
const declaredModernPackages = packageSource.modernPackages?.packages;
|
|
505
|
+
if (!Array.isArray(declaredModernPackages) || declaredModernPackages.length === 0) {
|
|
506
|
+
console.error('Package source metadata must declare the generated Modern package cohort');
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
508
509
|
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
}
|
|
510
|
+
const invalidModernPackages = declaredModernPackages.filter(
|
|
511
|
+
(packageName) => typeof packageName !== 'string' || !packageName.startsWith('@modern-js/'),
|
|
512
|
+
);
|
|
513
|
+
if (invalidModernPackages.length > 0) {
|
|
514
|
+
console.error(`Package source metadata contains invalid Modern packages: ${invalidModernPackages.join(', ')}`);
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const packageJsonModernDependencies = [
|
|
519
|
+
...new Set(
|
|
520
|
+
['dependencies', 'devDependencies']
|
|
521
|
+
.flatMap((section) => Object.keys(packageJson[section] ?? {}))
|
|
522
|
+
.filter((packageName) => packageName.startsWith('@modern-js/')),
|
|
523
|
+
),
|
|
524
|
+
];
|
|
525
|
+
const missingMetadataPackages = packageJsonModernDependencies.filter(
|
|
526
|
+
(packageName) => !declaredModernPackages.includes(packageName),
|
|
527
|
+
);
|
|
528
|
+
if (missingMetadataPackages.length > 0) {
|
|
529
|
+
console.error(`Package source metadata must include package.json Modern dependencies: ${missingMetadataPackages.join(', ')}`);
|
|
530
|
+
process.exit(1);
|
|
514
531
|
}
|
|
515
532
|
|
|
516
533
|
const expectedModernSpecifier = packageSource.modernPackages?.specifier;
|
|
@@ -535,32 +552,15 @@ const expectedModernDependency = (packageName) => {
|
|
|
535
552
|
: expectedModernSpecifier;
|
|
536
553
|
};
|
|
537
554
|
|
|
538
|
-
for (const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
])
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
console.error(`Dependency ${packageName} must match package source metadata`);
|
|
548
|
-
process.exit(1);
|
|
549
|
-
}
|
|
550
|
-
}
|
|
551
|
-
|
|
552
|
-
for (const packageName of [
|
|
553
|
-
'@modern-js/app-tools',
|
|
554
|
-
'@modern-js/adapter-rstest',
|
|
555
|
-
'@modern-js/tsconfig',
|
|
556
|
-
'@modern-js/plugin-bff',
|
|
557
|
-
]) {
|
|
558
|
-
if (
|
|
559
|
-
packageJson.devDependencies?.[packageName] &&
|
|
560
|
-
packageJson.devDependencies[packageName] !== expectedModernDependency(packageName)
|
|
561
|
-
) {
|
|
562
|
-
console.error(`Dev dependency ${packageName} must match package source metadata`);
|
|
563
|
-
process.exit(1);
|
|
555
|
+
for (const section of ['dependencies', 'devDependencies']) {
|
|
556
|
+
for (const packageName of declaredModernPackages) {
|
|
557
|
+
if (
|
|
558
|
+
packageJson[section]?.[packageName] &&
|
|
559
|
+
packageJson[section][packageName] !== expectedModernDependency(packageName)
|
|
560
|
+
) {
|
|
561
|
+
console.error(`${section}.${packageName} must match package source metadata`);
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
564
|
}
|
|
565
565
|
}
|
|
566
566
|
|
|
@@ -89,6 +89,7 @@ describe('generated UltraModern contract', () => {
|
|
|
89
89
|
modernjs?: {
|
|
90
90
|
packageSource?: {
|
|
91
91
|
config?: string;
|
|
92
|
+
strategy?: string;
|
|
92
93
|
};
|
|
93
94
|
preset?: string;
|
|
94
95
|
};
|
|
@@ -105,15 +106,20 @@ describe('generated UltraModern contract', () => {
|
|
|
105
106
|
expect(packageJson.modernjs?.packageSource?.config).toBe(
|
|
106
107
|
'./.modernjs/ultramodern-package-source.json',
|
|
107
108
|
);
|
|
108
|
-
expect(packageSource
|
|
109
|
-
|
|
110
|
-
'@modern-js/runtime',
|
|
111
|
-
);
|
|
112
|
-
expect(packageSource.modernPackages?.packages).toContain(
|
|
113
|
-
'@modern-js/app-tools',
|
|
109
|
+
expect(packageJson.modernjs?.packageSource?.strategy).toBe(
|
|
110
|
+
packageSource.strategy,
|
|
114
111
|
);
|
|
115
|
-
expect(packageSource.
|
|
116
|
-
|
|
112
|
+
expect(packageSource.strategy).toMatch(/^(workspace|install)$/u);
|
|
113
|
+
const generatedModernDependencies = [
|
|
114
|
+
...new Set(
|
|
115
|
+
(['dependencies', 'devDependencies'] as const).flatMap(section =>
|
|
116
|
+
Object.keys(packageJson[section] ?? {}),
|
|
117
|
+
),
|
|
118
|
+
),
|
|
119
|
+
].filter(packageName => packageName.startsWith('@modern-js/'));
|
|
120
|
+
expect(packageSource.modernPackages?.packages?.length).toBeGreaterThan(0);
|
|
121
|
+
expect(packageSource.modernPackages?.packages).toEqual(
|
|
122
|
+
expect.arrayContaining(generatedModernDependencies),
|
|
117
123
|
);
|
|
118
124
|
expect(packageSource.modernPackages?.specifier).toBeTruthy();
|
|
119
125
|
expect(
|
|
@@ -19,9 +19,18 @@ pnpm dlx @bleedingdev/modern-js-create payments --vertical
|
|
|
19
19
|
```
|
|
20
20
|
|
|
21
21
|
Each added vertical owns its UI/routes, browser-safe Module Federation exposes,
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
stay out of browser exposes.
|
|
22
|
+
private-first route metadata, localized URLs, public-route opt-ins, CSS prefix,
|
|
23
|
+
Effect BFF handlers, local API contract, and typed client surface. Server
|
|
24
|
+
handlers and Effect client/contract modules stay out of browser exposes.
|
|
25
|
+
|
|
26
|
+
## Private-First Public Surfaces
|
|
27
|
+
|
|
28
|
+
Generated app routes are private and non-indexable by default. Private app,
|
|
29
|
+
auth, tenant, dashboard, and internal routes publish no discovery output unless
|
|
30
|
+
route metadata explicitly marks them `public && indexable`. The default scaffold
|
|
31
|
+
therefore emits only a disallowing `robots.txt`; sitemap, web manifest,
|
|
32
|
+
`llms.txt`, API catalog, security.txt, and JSON-LD output stay omitted until a
|
|
33
|
+
safe public route or public docs/help/product surface exists.
|
|
25
34
|
|
|
26
35
|
Run the scaffold validator before adding business code and after every
|
|
27
36
|
`--vertical` mutation:
|