@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 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, and Zephyr dependency metadata.
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 localization is route-owned. Each app writes
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 ?? 'workspace';
918
- return {
971
+ const strategy = options.packageSource?.strategy ?? 'install';
972
+ if ('workspace' === strategy) return {
919
973
  strategy,
920
- modernPackageVersion: 'install' === strategy ? options.packageSource?.modernPackageVersion ?? options.modernVersion : WORKSPACE_PACKAGE_VERSION,
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
- function modernPackageVersion(packageSource) {
927
- return 'install' === packageSource.strategy ? packageSource.modernPackageVersion : WORKSPACE_PACKAGE_VERSION;
928
- }
929
- function modernAliasPackageName(packageName, packageSource) {
930
- if (!packageSource.aliasScope) return packageName;
931
- const scope = packageSource.aliasScope.replace(/^@/, '');
932
- const unscopedName = packageName.split('/').at(-1);
933
- return `@${scope}/${packageSource.aliasPackageNamePrefix ?? ''}${unscopedName}`;
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} html: {
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
- function workspaceAssetsForApp(_app) {
2139
- return {};
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('&', '&amp;').replaceAll('<', '&lt;').replaceAll('>', '&gt;');
2233
+ }
2234
+ function escapeXmlAttribute(value) {
2235
+ return escapeXmlText(value).replaceAll('"', '&quot;');
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.dependencies?.['@modern-js/plugin-bff'], \`\${vertical.id} must depend on plugin-bff\`);
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 '@bleedingdev/modern-js-create' === createPackage.name;
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: getOptionValue(args, [
6543
- '--ultramodern-package-registry'
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
- ]) ?? 'modern-js-'
6806
+ ]) ?? (aliasScope ? BLEEDINGDEV_PACKAGE_NAME_PREFIX : void 0)
6551
6807
  };
6552
6808
  }
6553
- function src_modernAliasPackageName(packageName, packageSource) {
6554
- if (!packageSource.aliasScope) return packageName;
6555
- const scope = packageSource.aliasScope.replace(/^@/, '');
6556
- const unscopedName = packageName.split('/').at(-1);
6557
- return `@${scope}/${packageSource.aliasPackageNamePrefix ?? ''}${unscopedName}`;
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 singleAppModernPackageSpecifier(packageName, packageSource, useWorkspaceProtocol) {
6560
- if (useWorkspaceProtocol) return 'workspace:*';
6561
- if ('install' !== packageSource.strategy || !packageSource.aliasScope) return packageSource.modernPackageVersion;
6562
- return `npm:${src_modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
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
- const singleAppModernPackages = [
6565
- '@modern-js/runtime',
6566
- '@modern-js/app-tools',
6567
- '@modern-js/tsconfig',
6568
- '@modern-js/plugin-i18n',
6569
- '@modern-js/plugin-tanstack',
6570
- '@modern-js/plugin-bff',
6571
- '@modern-js/adapter-rstest'
6572
- ];
6573
- function createSingleAppPackageSourceEvidence(packageSource, useWorkspaceProtocol) {
6574
- const strategy = useWorkspaceProtocol ? 'workspace' : 'install';
6575
- const specifier = useWorkspaceProtocol ? 'workspace:*' : packageSource.modernPackageVersion;
6576
- const aliases = 'install' === strategy && packageSource.aliasScope ? Object.fromEntries(singleAppModernPackages.map((packageName)=>[
6577
- packageName,
6578
- src_modernAliasPackageName(packageName, packageSource)
6579
- ])) : void 0;
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
- specifier,
6586
- packages: singleAppModernPackages,
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, useWorkspaceProtocol) {
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, useWorkspaceProtocol), null, 2)}\n`);
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: detectUltramodernPackageSource(args, ultramodernPackageVersion, createPackage)
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: useWorkspaceProtocol ? 'workspace:*' : packageSource.modernPackageVersion,
6838
- runtimeVersion: singleAppModernPackageSpecifier('@modern-js/runtime', packageSource, useWorkspaceProtocol),
6839
- appToolsVersion: singleAppModernPackageSpecifier('@modern-js/app-tools', packageSource, useWorkspaceProtocol),
6840
- adapterRstestVersion: singleAppModernPackageSpecifier('@modern-js/adapter-rstest', packageSource, useWorkspaceProtocol),
6841
- tsconfigVersion: singleAppModernPackageSpecifier('@modern-js/tsconfig', packageSource, useWorkspaceProtocol),
6842
- pluginTanstackVersion: singleAppModernPackageSpecifier('@modern-js/plugin-tanstack', packageSource, useWorkspaceProtocol),
6843
- pluginBffVersion: singleAppModernPackageSpecifier('@modern-js/plugin-bff', packageSource, useWorkspaceProtocol),
6844
- pluginI18nVersion: singleAppModernPackageSpecifier('@modern-js/plugin-i18n', packageSource, useWorkspaceProtocol),
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: useWorkspaceProtocol ? 'workspace' : 'install',
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, useWorkspaceProtocol);
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 = 'workspace' | 'install';
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.103",
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.103"
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.103"
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}} plugins: [
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: match[1].trim(),
171
+ text: visibleText.trim(),
62
172
  });
63
173
  }
64
174
  }
65
175
 
66
176
  for (const match of content.matchAll(jsxTextPattern)) {
67
- const text = match[1].replaceAll(/\s+/gu, ' ').trim();
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 expectedModernPackages = [
500
- '@modern-js/runtime',
501
- '@modern-js/app-tools',
502
- '@modern-js/tsconfig',
503
- '@modern-js/plugin-i18n',
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
- for (const packageName of expectedModernPackages) {
510
- if (!packageSource.modernPackages?.packages?.includes(packageName)) {
511
- console.error(`Package source metadata must include ${packageName}`);
512
- process.exit(1);
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 packageName of [
539
- '@modern-js/runtime',
540
- '@modern-js/plugin-i18n',
541
- '@modern-js/plugin-tanstack',
542
- ]) {
543
- if (
544
- packageJson.dependencies?.[packageName] &&
545
- packageJson.dependencies[packageName] !== expectedModernDependency(packageName)
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.strategy).toMatch(/^(workspace|install)$/u);
109
- expect(packageSource.modernPackages?.packages).toContain(
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.modernPackages?.packages).toContain(
116
- '@modern-js/adapter-rstest',
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
- localized route metadata, CSS prefix, Effect BFF handlers, local API contract,
23
- and typed client surface. Server handlers and Effect client/contract modules
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: