@bleedingdev/modern-js-create 3.2.0-ultramodern.82 → 3.2.0-ultramodern.84

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
@@ -27,24 +27,20 @@ To initialize the empty directory you are already in, pass `.` explicitly:
27
27
  pnpm dlx @bleedingdev/modern-js-create .
28
28
  ```
29
29
 
30
- Create a full SuperApp workspace only when you need independently owned
31
- verticals:
30
+ The default is the full UltraModern single-app setup. Create a SuperApp
31
+ workspace explicitly when you need independently owned verticals:
32
32
 
33
33
  ```bash
34
34
  pnpm dlx @bleedingdev/modern-js-create my-super-app --ultramodern-workspace
35
35
  ```
36
36
 
37
- The workspace is a full-stack reference, not a visual-only commerce boundary
38
- demo. It generates:
37
+ The workspace starts shell-only so the first commit has no fake business
38
+ domains to delete. It generates:
39
39
 
40
40
  - `apps/shell-super-app` as the Module Federation host and topology owner.
41
- - `verticals/explore` for discovery UI plus
42
- `/explore-api/effect/explore/*`.
43
- - `verticals/decide` for product selection UI plus
44
- `/decide-api/effect/decide/*`.
45
- - `verticals/checkout` for cart and checkout UI plus
46
- `/checkout-api/effect/checkout/*`.
47
- - `packages/shared-design-tokens` as the shared CSS token owner.
41
+ - `verticals/*` empty until you add a real domain with `--vertical`.
42
+ - `packages/shared-*` placeholders for shared contracts, tokens, and API
43
+ support.
48
44
  - `.modernjs/ultramodern-generated-contract.json` with MF, Effect, i18n,
49
45
  federated CSS, Cloudflare, and Zephyr dependency metadata.
50
46
 
@@ -54,13 +50,14 @@ Validate the generated workspace before making application changes:
54
50
  cd my-super-app
55
51
  mise install
56
52
  pnpm install
57
- pnpm ultramodern:check
53
+ pnpm check
58
54
  pnpm build
59
55
  ```
60
56
 
61
57
  ### Router Template
62
58
 
63
- TanStack Router is generated by default. To force the compatibility router:
59
+ TanStack Router is generated by default. Choose React Router only as an explicit
60
+ compatibility lane:
64
61
 
65
62
  ```bash
66
63
  pnpm dlx @bleedingdev/modern-js-create my-app --router react-router
@@ -127,10 +124,10 @@ Use this decision table before adding a vertical:
127
124
  | Design tokens, primitives, generated clients, or domain-neutral utilities | Yes | Use an ordinary workspace package, not a vertical |
128
125
  | Feature composites or workflow state shared across verticals | No | Revisit ownership; do not hide it in shared code |
129
126
 
130
- The lower-level `--router`, `--workspace`, and `--sub` flags remain available
131
- for manual package scaffolding, but UltraModern vertical additions should use
132
- `--vertical` so paths, topology, Effect BFF contracts, and local overlays stay
133
- consistent.
127
+ The lower-level `--router react-router`, `--workspace`, and `--sub` flags remain
128
+ available for compatibility and local package scaffolding, but UltraModern
129
+ vertical additions should use `--vertical` so paths, topology, Effect BFF
130
+ contracts, and local overlays stay consistent.
134
131
 
135
132
  See
136
133
  `docs/super-app-rfc-adr/WORKSPACE-0001-micro-vertical-workspace-scaffolding.md`
@@ -141,14 +138,13 @@ UltraModern.js entrypoint. The lower-level `--ultramodern-*` flags remain
141
138
  available for release engineering and local package-source testing, but users
142
139
  should not need them for normal app creation.
143
140
 
144
- ### Tractor Architecture Contracts
141
+ ### SuperApp Architecture Contracts
145
142
 
146
- The generated shell owns route assembly and policy. The generated Explore,
147
- Decide, and Checkout verticals own their own route subtree, MF exposes, Effect BFF
148
- contract, generated client, `localisedUrls`, locale JSON, CSS layer, and
149
- Cloudflare Worker output. The shell consumes vertical UI through MF manifests
150
- and vertical APIs through generated Effect clients exported by the vertical
151
- packages.
143
+ The generated shell owns route assembly and policy. Each vertical added with
144
+ `--vertical` owns its route subtree, MF exposes, Effect BFF contract, generated
145
+ client, `localisedUrls`, locale JSON, CSS layer, and Cloudflare Worker output.
146
+ The shell consumes vertical UI through MF manifests and vertical APIs through
147
+ generated Effect clients exported by the vertical packages.
152
148
 
153
149
  Route localization is route-owned. Each app writes
154
150
  `src/routes/ultramodern-route-metadata` and passes
@@ -176,7 +172,7 @@ manifest markers in lockstep for the same vertical version.
176
172
 
177
173
  ### Cloudflare And Zephyr Proof
178
174
 
179
- Each generated app has:
175
+ Each generated workspace app has:
180
176
 
181
177
  - `cloudflare:build`, `cloudflare:deploy`, `cloudflare:preview`, and
182
178
  `cloudflare:proof` scripts.
@@ -184,20 +180,33 @@ Each generated app has:
184
180
  - `zephyr:dependencies` for any consumed verticals.
185
181
  - `zephyr-rspack-plugin` wired through the generated Modern.js Rspack bridge.
186
182
 
187
- Public URL proof is intentionally separate from local build validation:
183
+ Deploy first, then pass each deployed app's generated public URL env key into
184
+ the proof step. Shell-only workspaces only need the shell URL; added verticals
185
+ use the same `ULTRAMODERN_PUBLIC_URL_<APP_ID>` pattern with hyphens converted
186
+ to underscores and uppercased:
188
187
 
189
188
  ```bash
190
- ULTRAMODERN_PUBLIC_URL_EXPLORE=https://explore.example.workers.dev \
191
- ULTRAMODERN_PUBLIC_URL_DECIDE=https://decide.example.workers.dev \
192
- ULTRAMODERN_PUBLIC_URL_CHECKOUT=https://checkout.example.workers.dev \
189
+ pnpm cloudflare:deploy
193
190
  ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP=https://shell-super-app.example.workers.dev \
191
+ ULTRAMODERN_PUBLIC_URL_TRANSPORTATION=https://transportation.example.workers.dev \
194
192
  pnpm cloudflare:proof -- --require-public-urls
195
193
  ```
196
194
 
197
- Live Zephyr switching proof requires Zephyr credentials and public runtime,
198
- manifest, and API URLs for v1 and v2 of Explore, Decide, and Checkout. Without
199
- public URLs and credentials, use dry-run evidence only; do not claim live
200
- Zephyr selection has been proven.
195
+ Without public URLs and credentials, use local `pnpm check` and `pnpm build`
196
+ evidence only; do not claim live Cloudflare or Zephyr selection has been
197
+ proven.
198
+
199
+ ### Troubleshooting
200
+
201
+ | Symptom | Current check | Owner |
202
+ | --- | --- | --- |
203
+ | Package cohort mismatch | Regenerate with one package source strategy, run `mise install`, then rerun `pnpm install` from the activated shell. | Generated workspace package source metadata |
204
+ | Install failure | Check the active Node/pnpm from `mise install`; rerun `pnpm install` after the shell sees the pinned versions. | Toolchain setup |
205
+ | Build failure | Run `pnpm check` before `pnpm build`; fix reported format, lint, type, skill, i18n, or generated-contract failures first. | Owning package or generated contract |
206
+ | Missing public URL | Set the env key from `.modernjs/ultramodern-generated-contract.json`, for example `ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP`. | Deployment operator |
207
+ | Cloudflare credentials | Confirm Wrangler credentials before `pnpm cloudflare:deploy`; local checks do not prove live Worker access. | Deployment operator |
208
+ | Asset or CSS 404 | Rebuild with `pnpm build` or `pnpm cloudflare:deploy` and inspect emitted Modern/Rspack asset paths instead of hardcoding CSS URLs. | Framework/runtime asset pipeline |
209
+ | Federation manifest failure | Run the shell and vertical build scripts, then check each deployed `/mf-manifest.json` URL used by the shell. | Module Federation owner |
201
210
 
202
211
  ### Local Monorepo Testing
203
212
 
@@ -208,14 +217,14 @@ workspace protocol dependencies:
208
217
  pnpm dlx @bleedingdev/modern-js-create my-app --workspace
209
218
  ```
210
219
 
211
- For package-source validation of the full Tractor workspace, generate with the
220
+ For package-source validation of the full SuperApp workspace, generate with the
212
221
  workspace package source, then run the generated contract gate:
213
222
 
214
223
  ```bash
215
- pnpm dlx @bleedingdev/modern-js-create tractor-super-app --ultramodern-workspace --ultramodern-package-source workspace
216
- cd tractor-super-app
224
+ pnpm dlx @bleedingdev/modern-js-create my-super-app --ultramodern-workspace --ultramodern-package-source workspace
225
+ cd my-super-app
217
226
  pnpm install
218
- pnpm ultramodern:check
227
+ pnpm check
219
228
  ```
220
229
 
221
230
  ## Documentation
package/dist/index.js CHANGED
@@ -465,12 +465,12 @@ const EN_LOCALE = {
465
465
  optionHelp: ' -h, --help Display this help message',
466
466
  optionVersion: ' -v, --version Display version information',
467
467
  optionLang: ' -l, --lang Set the language (zh or en)',
468
- optionRouter: ' -r, --router Select router framework (react-router or tanstack)',
468
+ optionRouter: ' -r, --router Select router framework (tanstack default; react-router is compatibility mode)',
469
469
  optionBff: ' --bff Keep Effect BFF enabled (default for UltraModern apps)',
470
470
  optionBffRuntime: ' --bff-runtime Select BFF runtime (hono or effect)',
471
471
  optionTailwind: ' --no-tailwind Disable default Tailwind CSS v4 scaffold',
472
472
  optionWorkspace: ' --workspace Use workspace protocol for @modern-js dependencies (for local monorepo testing)',
473
- optionUltramodernWorkspace: ' --ultramodern-workspace Generate an UltraModern SuperApp workspace (explicit opt-in; default is a simple app)',
473
+ optionUltramodernWorkspace: ' --ultramodern-workspace Generate an UltraModern SuperApp workspace (default is a full UltraModern single app)',
474
474
  optionUltramodernPackageSource: ' --ultramodern-package-source Select UltraModern package source (workspace or install; BleedingDev defaults to install aliases)',
475
475
  optionUltramodernPackageScope: ' --ultramodern-package-scope Publish scope for npm alias installs (for example bleedingdev)',
476
476
  optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix Prefix for npm alias package names (default: modern-js-)',
@@ -487,7 +487,7 @@ const EN_LOCALE = {
487
487
  example8: ' pnpm dlx @bleedingdev/modern-js-create my-app --workspace',
488
488
  example9: ' pnpm dlx @bleedingdev/modern-js-create my-super-app --ultramodern-workspace',
489
489
  example10: ' pnpm dlx @bleedingdev/modern-js-create my-app --no-tailwind',
490
- example11: ' pnpm dlx @bleedingdev/modern-js-create my-app --router react-router',
490
+ example11: ' pnpm dlx @bleedingdev/modern-js-create my-app --router react-router # compatibility mode',
491
491
  example12: ' pnpm dlx @bleedingdev/modern-js-create catalog --vertical',
492
492
  moreInfo: '📚 Learn more: https://modernjs.dev'
493
493
  },
@@ -523,12 +523,12 @@ const ZH_LOCALE = {
523
523
  optionHelp: ' -h, --help 显示帮助信息',
524
524
  optionVersion: ' -v, --version 显示版本信息',
525
525
  optionLang: ' -l, --lang 设置语言 (zh 或 en)',
526
- optionRouter: ' -r, --router 选择路由框架 (react-router 或 tanstack)',
526
+ optionRouter: ' -r, --router 选择路由框架(默认 tanstack;react-router 为兼容模式)',
527
527
  optionBff: ' --bff 保持启用 Effect BFF(UltraModern 应用默认值)',
528
528
  optionBffRuntime: ' --bff-runtime 选择 BFF 运行时(hono 或 effect)',
529
529
  optionTailwind: ' --no-tailwind 禁用默认 Tailwind CSS v4 模板',
530
530
  optionWorkspace: ' --workspace 对 @modern-js 依赖使用 workspace 协议(用于本地 monorepo 联调)',
531
- optionUltramodernWorkspace: ' --ultramodern-workspace 生成 UltraModern SuperApp 工作区(显式选择;默认创建简单应用)',
531
+ optionUltramodernWorkspace: ' --ultramodern-workspace 生成 UltraModern SuperApp 工作区(默认创建完整 UltraModern 单应用)',
532
532
  optionUltramodernPackageSource: ' --ultramodern-package-source 选择 UltraModern 依赖来源(workspace 或 install;BleedingDev 默认使用 install alias)',
533
533
  optionUltramodernPackageScope: ' --ultramodern-package-scope npm alias 安装使用的发布 scope(例如 bleedingdev)',
534
534
  optionUltramodernPackageNamePrefix: ' --ultramodern-package-name-prefix npm alias 包名前缀(默认:modern-js-)',
@@ -545,7 +545,7 @@ const ZH_LOCALE = {
545
545
  example8: ' pnpm dlx @bleedingdev/modern-js-create my-app --workspace',
546
546
  example9: ' pnpm dlx @bleedingdev/modern-js-create my-super-app --ultramodern-workspace',
547
547
  example10: ' pnpm dlx @bleedingdev/modern-js-create my-app --no-tailwind',
548
- example11: ' pnpm dlx @bleedingdev/modern-js-create my-app --router react-router',
548
+ example11: ' pnpm dlx @bleedingdev/modern-js-create my-app --router react-router # 兼容模式',
549
549
  example12: ' pnpm dlx @bleedingdev/modern-js-create catalog --vertical',
550
550
  moreInfo: '📚 更多信息: https://modernjs.dev'
551
551
  },
@@ -619,6 +619,19 @@ function sortJsonValue(value) {
619
619
  return value;
620
620
  }
621
621
  const ULTRAMODERN_WORKSPACE_FLAG = '--ultramodern-workspace';
622
+ const FIRST_VERTICAL_PORT = 4101;
623
+ const TAILWIND_PREFIX_DIGIT_WORDS = [
624
+ 'zero',
625
+ 'one',
626
+ 'two',
627
+ 'three',
628
+ 'four',
629
+ 'five',
630
+ 'six',
631
+ 'seven',
632
+ 'eight',
633
+ 'nine'
634
+ ];
622
635
  function isRecord(value) {
623
636
  return null !== value && 'object' == typeof value && !Array.isArray(value);
624
637
  }
@@ -1115,6 +1128,7 @@ function createTsConfigBase() {
1115
1128
  verbatimModuleSyntax: true,
1116
1129
  strict: true,
1117
1130
  noEmit: true,
1131
+ allowImportingTsExtensions: true,
1118
1132
  allowJs: true,
1119
1133
  esModuleInterop: true,
1120
1134
  noUncheckedIndexedAccess: true,
@@ -1228,6 +1242,7 @@ function createAppModernConfig(scope, app) {
1228
1242
  const bffImport = appHasEffectApi(app) ? "import { bffPlugin } from '@modern-js/plugin-bff';\n" : '';
1229
1243
  const bffConfig = appHasEffectApi(app) ? ` bff: {
1230
1244
  effect: {
1245
+ entry: './api/effect/index',
1231
1246
  openapi: {
1232
1247
  path: '/openapi.json',
1233
1248
  },
@@ -1532,6 +1547,9 @@ const reactDomVersion = (require('react-dom/package.json') as { version: string
1532
1547
 
1533
1548
  ${createModuleFederationRemoteUrlHelpers(shellHost, remotes)}
1534
1549
  export default createModuleFederationConfig({
1550
+ bridge: {
1551
+ enableBridgeRouter: false,
1552
+ },
1535
1553
  dev: {
1536
1554
  disableDynamicRemoteTypeHints: true,
1537
1555
  },
@@ -1587,6 +1605,9 @@ const reactDomVersion = (require('react-dom/package.json') as { version: string
1587
1605
 
1588
1606
  ${createModuleFederationRemoteUrlHelpers(app, remotes)}
1589
1607
  export default createModuleFederationConfig({
1608
+ bridge: {
1609
+ enableBridgeRouter: false,
1610
+ },
1590
1611
  dev: {
1591
1612
  disableDynamicRemoteTypeHints: true,
1592
1613
  },
@@ -1910,9 +1931,9 @@ function createCssTokenImport(scope) {
1910
1931
  return `@import '${ultramodern_workspace_packageName(scope, 'shared-design-tokens')}/tokens.css';\n`;
1911
1932
  }
1912
1933
  function createTailwindPrefix(raw) {
1913
- const prefix = raw.toLowerCase().replace(/[^a-z]/gu, '');
1914
- if (!prefix) throw new Error(`Cannot derive a Tailwind prefix from ${raw}`);
1915
- return prefix;
1934
+ const normalized = raw.toLowerCase().replace(/[^a-z0-9]/gu, '');
1935
+ if (!normalized) throw new Error(`Cannot derive a Tailwind prefix from ${raw}`);
1936
+ return normalized.replace(/[0-9]/gu, (digit)=>TAILWIND_PREFIX_DIGIT_WORDS[Number(digit)]);
1916
1937
  }
1917
1938
  function tailwindPrefixForApp(app) {
1918
1939
  if ('shell' === app.kind) return 'shell';
@@ -2350,24 +2371,16 @@ function createShellRemoteComponents(scope, remotes = []) {
2350
2371
  const componentName = `${toPascalCase(remote.id)}Widget`;
2351
2372
  return `const ${componentName} = createHydratedRemote(${componentName}Server, '${remoteDependencyAlias(remote)}/Widget');`;
2352
2373
  }).join('\n');
2353
- const showcaseItems = widgetRemotes.map((remote)=>{
2354
- const componentName = `${toPascalCase(remote.id)}Widget`;
2355
- return ` <${componentName} key="${remote.id}" />`;
2356
- }).join('\n');
2357
- const remoteCount = String(widgetRemotes.length);
2358
- return `import { createLazyComponent } from '@module-federation/bridge-react';
2374
+ const federationImports = widgetRemotes.length > 0 ? `import { createLazyComponent } from '@module-federation/bridge-react';
2359
2375
  import { getInstance, loadRemote } from '@module-federation/modern-js-v3/runtime';
2360
2376
  import { Suspense, useEffect, useMemo, useState } from 'react';
2361
2377
  import type { ComponentType } from 'react';
2362
- import { I18nLink, useModernI18n } from '@modern-js/plugin-i18n/runtime';
2363
2378
  ${serverImports}
2364
-
2365
- interface RemoteComponentModule {
2379
+ ` : '';
2380
+ const federationHelpers = widgetRemotes.length > 0 ? `interface RemoteComponentModule {
2366
2381
  default: ComponentType;
2367
2382
  }
2368
2383
 
2369
- const widgetCount = Number('${remoteCount}');
2370
-
2371
2384
  const loadRemoteComponent = (specifier: string) =>
2372
2385
  loadRemote<RemoteComponentModule>(specifier) as Promise<RemoteComponentModule>;
2373
2386
 
@@ -2414,10 +2427,20 @@ const createHydratedRemote =
2414
2427
  </Suspense>
2415
2428
  );
2416
2429
  };
2430
+ ` : '';
2431
+ const showcaseItems = widgetRemotes.map((remote)=>{
2432
+ const componentName = `${toPascalCase(remote.id)}Widget`;
2433
+ return ` <${componentName} key="${remote.id}" />`;
2434
+ }).join('\n');
2435
+ const remoteCount = String(widgetRemotes.length);
2436
+ return `${federationImports}import { I18nLink, useModernI18n } from '@modern-js/plugin-i18n/runtime';
2437
+
2438
+ const widgetCount = Number('${remoteCount}');
2417
2439
 
2418
- ${hydratedExports}
2440
+ ${federationHelpers}
2441
+ ${hydratedExports}
2419
2442
 
2420
- export const Header = () => {
2443
+ export const Header = () => {
2421
2444
  const { i18nInstance } = useModernI18n();
2422
2445
  const t = i18nInstance['t'].bind(i18nInstance);
2423
2446
 
@@ -3442,7 +3465,7 @@ function createEffectServiceEntry(scope, service, contractImportPath = ultramode
3442
3465
  HttpApiBuilder,
3443
3466
  Layer,
3444
3467
  } from '@modern-js/plugin-bff/effect-edge';
3445
- import { ultramodernApiMarker } from '../../src/ultramodern-build';
3468
+ import { ultramodernApiMarker } from '../../src/ultramodern-build.ts';
3446
3469
  import {
3447
3470
  ${apiExport},
3448
3471
  ${groupName}OperationContexts,
@@ -4685,6 +4708,7 @@ function createWorkspaceValidationScript(scope, enableTailwind, remotes = []) {
4685
4708
  mfName: remote.mfName,
4686
4709
  apiPrefix: remote.effectApi.prefix,
4687
4710
  tailwindPrefix: tailwindPrefixForApp(remote),
4711
+ zephyrAlias: remoteDependencyAlias(remote),
4688
4712
  packageName: ultramodern_workspace_packageName(scope, remote.packageSuffix),
4689
4713
  exposes: Object.keys(remote.exposes ?? {}),
4690
4714
  componentPaths: Object.keys(remote.exposes ?? {}).map((expose)=>remoteComponentOutputPath(remote, expose)).filter((componentPath)=>Boolean(componentPath)),
@@ -4873,7 +4897,7 @@ assert(generatedContract.cssFederation?.sharedDesignTokens?.ssr?.firstPaintRequi
4873
4897
  const shellPackage = readJson('apps/shell-super-app/package.json');
4874
4898
  const expectedZephyrDependencies = Object.fromEntries(
4875
4899
  fullStackVerticals.map(vertical => [
4876
- vertical.domain,
4900
+ vertical.zephyrAlias,
4877
4901
  \`\${vertical.packageName}@workspace:*\`,
4878
4902
  ]),
4879
4903
  );
@@ -4917,7 +4941,7 @@ for (const vertical of fullStackVerticals) {
4917
4941
  fullStackVerticals
4918
4942
  .filter(candidate => vertical.verticalRefs.includes(candidate.id))
4919
4943
  .map(candidate => [
4920
- candidate.domain,
4944
+ candidate.zephyrAlias,
4921
4945
  \`\${candidate.packageName}@workspace:*\`,
4922
4946
  ]),
4923
4947
  );
@@ -4960,7 +4984,7 @@ for (const vertical of fullStackVerticals) {
4960
4984
  assert(contractEntry?.styling?.federation?.owner?.id === vertical.id, \`\${vertical.id} CSS federation owner is missing\`);
4961
4985
  assert(contractEntry?.styling?.federation?.role === 'vertical-css', \`\${vertical.id} must own only vertical CSS\`);
4962
4986
  assert(contractEntry?.styling?.federation?.rootSelector === \`[data-app-id="\${vertical.id}"]\`, \`\${vertical.id} CSS root selector is incorrect\`);
4963
- assert(contractEntry?.styling?.federation?.classPrefix === \`\${vertical.domain}:\`, \`\${vertical.id} CSS class prefix is incorrect\`);
4987
+ assert(contractEntry?.styling?.federation?.classPrefix === \`\${vertical.tailwindPrefix}:\`, \`\${vertical.id} CSS class prefix is incorrect\`);
4964
4988
  assert(contractEntry?.styling?.federation?.layers?.owned?.includes(\`ultramodern-vertical-\${vertical.domain}\`), \`\${vertical.id} vertical CSS layer is missing\`);
4965
4989
  assert(!contractEntry?.styling?.federation?.layers?.owned?.includes('ultramodern-shell-base'), \`\${vertical.id} must not own shell base CSS\`);
4966
4990
  assert(contractEntry?.styling?.federation?.entrypoints?.federationEntry === 'src/federation-entry.tsx', \`\${vertical.id} CSS contract must include federation entry\`);
@@ -5364,7 +5388,7 @@ function writeApp(targetDir, scope, app, packageSource, enableTailwind, remotes
5364
5388
  }
5365
5389
  if (appHasEffectApi(resolvedApp)) {
5366
5390
  writeFile(targetDir, `${resolvedApp.directory}/shared/effect/api.ts`, createEffectSharedApi(resolvedApp));
5367
- writeFile(targetDir, `${resolvedApp.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, resolvedApp, '../../shared/effect/api'));
5391
+ writeFile(targetDir, `${resolvedApp.directory}/api/effect/index.ts`, createEffectServiceEntry(scope, resolvedApp, '../../shared/effect/api.ts'));
5368
5392
  writeFile(targetDir, `${resolvedApp.directory}/src/effect/${resolvedApp.effectApi.stem}-client.ts`, createEffectClient(resolvedApp, '../../shared/effect/api'));
5369
5393
  }
5370
5394
  if ('vertical' === resolvedApp.kind) {
@@ -5454,7 +5478,7 @@ function assertValidVerticalName(name) {
5454
5478
  }
5455
5479
  function nextAvailablePort(ports) {
5456
5480
  const numericPorts = Object.values(ports).filter((value)=>'number' == typeof value && Number.isFinite(value));
5457
- return Math.max(3030, ...numericPorts) + 1;
5481
+ return Math.max(FIRST_VERTICAL_PORT - 1, ...numericPorts) + 1;
5458
5482
  }
5459
5483
  function assertCanCreate(workspaceRoot, relativePath) {
5460
5484
  if (node_fs.existsSync(node_path.join(workspaceRoot, relativePath))) throw new Error(`Refusing to overwrite existing path: ${relativePath}`);
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
24
- "version": "3.2.0-ultramodern.82",
24
+ "version": "3.2.0-ultramodern.84",
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.82"
44
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.84"
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.82"
57
+ "frameworkVersion": "3.2.0-ultramodern.84"
58
58
  }
59
59
  }
@@ -10,7 +10,7 @@ This project is generated for Codex-first UltraModern.js work.
10
10
  - `pnpm i18n:check` rejects hardcoded user-visible JSX text.
11
11
  - `pnpm ultramodern:check` verifies the generated contract.
12
12
  - Generated Codex stop hooks and subagent-stop hooks run `pnpm format && pnpm lint:fix && pnpm ultramodern:check`.
13
- - `postinstall` installs `lefthook` when the app is inside a Git worktree. Generated `lefthook.yml` runs `pnpm format && pnpm lint:fix && pnpm ultramodern:check` on pre-commit; pre-push runs `pnpm ultramodern:check`.
13
+ - `postinstall` formats the generated tree, installs private orchestration skills when available, initializes a Git repository when needed, and installs `lefthook`. Generated `lefthook.yml` runs `pnpm format && pnpm lint:fix && pnpm ultramodern:check` on pre-commit; pre-push runs `pnpm ultramodern:check`.
14
14
 
15
15
  ## Internationalization
16
16
 
@@ -3,6 +3,13 @@ import ultracite from 'ultracite/oxfmt';
3
3
 
4
4
  export default defineConfig({
5
5
  extends: [ultracite],
6
- ignorePatterns: ['dist', 'node_modules', '.modern', '.modernjs', '**/routeTree.gen.ts'],
6
+ ignorePatterns: [
7
+ '.agents',
8
+ 'dist',
9
+ 'node_modules',
10
+ '.modern',
11
+ '.modernjs',
12
+ '**/routeTree.gen.ts',
13
+ ],
7
14
  singleQuote: true,
8
15
  });
@@ -8,5 +8,12 @@ export default defineConfig({
8
8
  node: true,
9
9
  },
10
10
  extends: [core, react],
11
- ignorePatterns: ['dist', 'node_modules', '.modern', '.modernjs', '**/routeTree.gen.ts'],
11
+ ignorePatterns: [
12
+ '.agents',
13
+ 'dist',
14
+ 'node_modules',
15
+ '.modern',
16
+ '.modernjs',
17
+ '**/routeTree.gen.ts',
18
+ ],
12
19
  });
@@ -15,7 +15,7 @@
15
15
  {{#unless isSubproject}}
16
16
  "skills:install": "node ./scripts/bootstrap-agent-skills.mjs",
17
17
  "skills:check": "node ./scripts/bootstrap-agent-skills.mjs --check",
18
- "postinstall": "node ./scripts/bootstrap-agent-skills.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true)",
18
+ "postinstall": "oxfmt . && node ./scripts/bootstrap-agent-skills.mjs",
19
19
  {{/unless}}
20
20
  "ultramodern:check": "{{#unless isSubproject}}pnpm format:check && pnpm lint && {{/unless}}pnpm typecheck && pnpm i18n:check && pnpm test{{#unless isSubproject}} && pnpm skills:check{{/unless}} && node ./scripts/validate-ultramodern.mjs"{{#unless isSubproject}},
21
21
  "format": "oxfmt .",
@@ -41,7 +41,7 @@
41
41
  "@modern-js/app-tools": "{{appToolsVersion}}",
42
42
  {{#if enableBff}} "@modern-js/plugin-bff": "{{pluginBffVersion}}",
43
43
  {{/if}} "@modern-js/tsconfig": "{{tsconfigVersion}}",
44
- "@rstest/core": "0.10.2",
44
+ "@rstest/core": "0.10.3",
45
45
  {{#if enableTailwind}}
46
46
  "@tailwindcss/postcss": "^{{tailwindPostcssVersion}}",
47
47
  {{/if}}
@@ -1,6 +1,8 @@
1
1
  minimumReleaseAge: 1440
2
2
  minimumReleaseAgeStrict: true
3
3
  minimumReleaseAgeIgnoreMissingTime: false
4
+ minimumReleaseAgeExclude:
5
+ - '@bleedingdev/modern-js-*'
4
6
  trustPolicy: no-downgrade
5
7
  trustPolicyIgnoreAfter: 1440
6
8
  blockExoticSubdeps: true
@@ -17,3 +19,11 @@ allowBuilds:
17
19
  msgpackr-extract: true
18
20
  sharp: true
19
21
  workerd: true
22
+ onlyBuiltDependencies:
23
+ - '@swc/core'
24
+ - core-js
25
+ - esbuild
26
+ - lefthook
27
+ - msgpackr-extract
28
+ - sharp
29
+ - workerd
@@ -1,7 +1,5 @@
1
- import { withModernConfig } from '@modern-js/adapter-rstest';
2
1
  import { defineConfig } from '@rstest/core';
3
2
 
4
3
  export default defineConfig({
5
- extends: withModernConfig(),
6
- testEnvironment: 'happy-dom',
4
+ testEnvironment: 'node',
7
5
  });
@@ -17,6 +17,76 @@ const run = (command, args, options = {}) =>
17
17
  stdio: options.stdio ?? ['ignore', 'pipe', 'pipe'],
18
18
  });
19
19
 
20
+ const commandExists = (command) => {
21
+ try {
22
+ run(command, ['--version'], { stdio: 'ignore' });
23
+ return true;
24
+ } catch {
25
+ return false;
26
+ }
27
+ };
28
+
29
+ const runShell = (script) =>
30
+ run('sh', ['-lc', script], {
31
+ stdio: 'inherit',
32
+ });
33
+
34
+ const installGit = () => {
35
+ if (commandExists('git')) {
36
+ return;
37
+ }
38
+
39
+ if (commandExists('brew')) {
40
+ run('brew', ['install', 'git'], { stdio: 'inherit' });
41
+ } else if (process.platform === 'linux' && commandExists('apt-get')) {
42
+ const sudo = typeof process.getuid === 'function' && process.getuid() === 0 ? '' : 'sudo ';
43
+ runShell(`${sudo}apt-get update && ${sudo}apt-get install -y git`);
44
+ } else if (process.platform === 'linux' && commandExists('dnf')) {
45
+ const sudo = typeof process.getuid === 'function' && process.getuid() === 0 ? '' : 'sudo ';
46
+ runShell(`${sudo}dnf install -y git`);
47
+ } else if (process.platform === 'linux' && commandExists('yum')) {
48
+ const sudo = typeof process.getuid === 'function' && process.getuid() === 0 ? '' : 'sudo ';
49
+ runShell(`${sudo}yum install -y git`);
50
+ } else if (process.platform === 'linux' && commandExists('apk')) {
51
+ runShell('apk add --no-cache git');
52
+ }
53
+
54
+ if (!commandExists('git')) {
55
+ throw new Error(
56
+ 'Git is required for UltraModern setup. Install git and run pnpm skills:install again.',
57
+ );
58
+ }
59
+ };
60
+
61
+ const isInsideGitWorkTree = () => {
62
+ try {
63
+ return run('git', ['rev-parse', '--is-inside-work-tree']).trim() === 'true';
64
+ } catch {
65
+ return false;
66
+ }
67
+ };
68
+
69
+ const initializeGitRepository = () => {
70
+ if (isInsideGitWorkTree()) {
71
+ return;
72
+ }
73
+
74
+ try {
75
+ run('git', ['init', '-b', 'main'], { stdio: 'inherit' });
76
+ } catch {
77
+ run('git', ['init'], { stdio: 'inherit' });
78
+ run('git', ['branch', '-M', 'main'], { stdio: 'inherit' });
79
+ }
80
+ };
81
+
82
+ const installLefthook = () => {
83
+ try {
84
+ run('lefthook', ['install'], { stdio: 'inherit' });
85
+ } catch (error) {
86
+ console.warn(`Unable to install lefthook hooks: ${error.message}`);
87
+ }
88
+ };
89
+
20
90
  const removeTree = (dir) =>
21
91
  fs.rmSync(dir, {
22
92
  force: true,
@@ -98,6 +168,7 @@ if (checkOnly) {
98
168
  }
99
169
 
100
170
  fs.mkdirSync(installDir, { recursive: true });
171
+ installGit();
101
172
 
102
173
  for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
103
174
  const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), 'ultramodern-skills-'));
@@ -133,3 +204,6 @@ for (const source of [...requiredCloneSources, ...optionalCloneSources]) {
133
204
  removeTree(tempDir);
134
205
  }
135
206
  }
207
+
208
+ initializeGitRepository();
209
+ installLefthook();
@@ -30,6 +30,13 @@ const collectFiles = (directory) => {
30
30
  };
31
31
 
32
32
  const lineNumberForIndex = (content, index) => content.slice(0, index).split('\n').length;
33
+ const isCodeElementText = (content, index) => {
34
+ const tagStart = content.lastIndexOf('<', index);
35
+ if (tagStart === -1) {
36
+ return false;
37
+ }
38
+ return /^<code(?:\s|>)/u.test(content.slice(tagStart, index));
39
+ };
33
40
  const isIgnoredLine = (content, index) => {
34
41
  const lineStart = content.lastIndexOf('\n', index) + 1;
35
42
  const lineEnd = content.indexOf('\n', index);
@@ -58,7 +65,11 @@ for (const filePath of scanRoots.flatMap(collectFiles)) {
58
65
 
59
66
  for (const match of content.matchAll(jsxTextPattern)) {
60
67
  const text = match[1].replaceAll(/\s+/gu, ' ').trim();
61
- if (text && !isIgnoredLine(content, match.index ?? 0)) {
68
+ if (
69
+ text &&
70
+ !isIgnoredLine(content, match.index ?? 0) &&
71
+ !isCodeElementText(content, match.index ?? 0)
72
+ ) {
62
73
  violations.push({
63
74
  filePath,
64
75
  line: lineNumberForIndex(content, match.index ?? 0),
@@ -9,21 +9,19 @@ const packageSourcePath = path.resolve(
9
9
  '.modernjs/ultramodern-package-source.json',
10
10
  );
11
11
  const readPnpmConfig = (key) => {
12
- const env = { ...process.env };
13
- for (const envKey of Object.keys(env)) {
14
- if (/^(?:npm|pnpm)_config_/i.test(envKey)) {
15
- delete env[envKey];
16
- }
17
- }
12
+ const env = Object.fromEntries(
13
+ Object.entries(process.env).filter(
14
+ ([envKey]) => !/^(?:npm|pnpm)_config_/iu.test(envKey),
15
+ ),
16
+ );
18
17
  const output = execFileSync('pnpm', ['config', 'get', key, '--json'], {
19
18
  cwd: process.cwd(),
20
- env,
21
19
  encoding: 'utf-8',
20
+ env,
22
21
  stdio: ['ignore', 'pipe', 'pipe'],
23
22
  }).trim();
24
23
  return output ? JSON.parse(output) : undefined;
25
24
  };
26
- const isSubproject = {{isSubproject}};
27
25
  const enableTailwind = {{enableTailwind}};
28
26
  const expectedPnpmVersion = '{{pnpmVersion}}';
29
27
  const activePnpmVersion = execFileSync('pnpm', ['--version'], {
@@ -131,14 +129,13 @@ if (fs.existsSync(path.resolve(process.cwd(), 'src/routes/page.tsx'))) {
131
129
  process.exit(1);
132
130
  }
133
131
 
134
- if (!enableTailwind) {
135
- if (
136
- fs.existsSync(path.resolve(process.cwd(), 'postcss.config.mjs')) ||
137
- fs.existsSync(path.resolve(process.cwd(), 'tailwind.config.ts'))
138
- ) {
139
- console.error('Tailwind config files must not be written when Tailwind is disabled');
140
- process.exit(1);
141
- }
132
+ if (
133
+ !enableTailwind &&
134
+ (fs.existsSync(path.resolve(process.cwd(), 'postcss.config.mjs')) ||
135
+ fs.existsSync(path.resolve(process.cwd(), 'tailwind.config.ts')))
136
+ ) {
137
+ console.error('Tailwind config files must not be written when Tailwind is disabled');
138
+ process.exit(1);
142
139
  }
143
140
 
144
141
  {{#unless isSubproject}}
@@ -259,17 +256,19 @@ const skillsLock = JSON.parse(
259
256
  );
260
257
  {{/unless}}
261
258
  const requiredScripts = {
262
- 'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
263
- test: 'rstest run',
264
259
  {{#unless isSubproject}}
265
260
  format: 'oxfmt .',
266
261
  'format:check': 'oxfmt --check .',
262
+ {{/unless}}
263
+ 'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
264
+ {{#unless isSubproject}}
267
265
  lint: 'oxlint .',
268
266
  'lint:fix': 'oxlint . --fix',
267
+ postinstall: 'oxfmt . && node ./scripts/bootstrap-agent-skills.mjs',
269
268
  'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
270
269
  'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
271
- postinstall: 'node ./scripts/bootstrap-agent-skills.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true)',
272
270
  {{/unless}}
271
+ test: 'rstest run',
273
272
  };
274
273
 
275
274
  for (const [scriptName, scriptCommand] of Object.entries(requiredScripts)) {
@@ -330,8 +329,11 @@ if (readPnpmConfig('minimumReleaseAgeIgnoreMissingTime') !== false) {
330
329
  console.error('pnpm minimumReleaseAgeIgnoreMissingTime must be false');
331
330
  process.exit(1);
332
331
  }
333
- if (readPnpmConfig('minimumReleaseAgeExclude') !== undefined) {
334
- console.error('pnpm minimumReleaseAgeExclude must stay unset');
332
+ if (
333
+ JSON.stringify(readPnpmConfig('minimumReleaseAgeExclude')) !==
334
+ JSON.stringify(['@bleedingdev/modern-js-*'])
335
+ ) {
336
+ console.error('pnpm minimumReleaseAgeExclude must allow just-published BleedingDev Modern cohorts only');
335
337
  process.exit(1);
336
338
  }
337
339
  if (readPnpmConfig('trustPolicy') !== 'no-downgrade') {
@@ -366,8 +368,11 @@ if (
366
368
  console.error('pnpm allowBuilds must approve only the generated app build dependencies');
367
369
  process.exit(1);
368
370
  }
369
- if (readPnpmConfig('onlyBuiltDependencies') !== undefined) {
370
- console.error('pnpm onlyBuiltDependencies must not be set');
371
+ if (
372
+ JSON.stringify(readPnpmConfig('onlyBuiltDependencies')) !==
373
+ JSON.stringify(['@swc/core', 'core-js', 'esbuild', 'lefthook', 'msgpackr-extract', 'sharp', 'workerd'])
374
+ ) {
375
+ console.error('pnpm onlyBuiltDependencies must approve only the generated app build dependencies');
371
376
  process.exit(1);
372
377
  }
373
378
 
@@ -420,6 +425,15 @@ if (typeof expectedModernSpecifier !== 'string' || expectedModernSpecifier.lengt
420
425
  console.error('Package source metadata must provide a Modern package specifier');
421
426
  process.exit(1);
422
427
  }
428
+ if (
429
+ packageSource.strategy === 'install' &&
430
+ expectedModernSpecifier !== templateManifest.template?.version
431
+ ) {
432
+ console.error(
433
+ `Package source Modern specifier ${expectedModernSpecifier} must match template version ${templateManifest.template?.version}`,
434
+ );
435
+ process.exit(1);
436
+ }
423
437
 
424
438
  const expectedModernDependency = (packageName) => {
425
439
  const alias = packageSource.modernPackages?.aliases?.[packageName];
@@ -121,16 +121,12 @@ const Index = () => {
121
121
  <p className="name">{t('home.name')}</p>
122
122
  </div>
123
123
  <p className="description{{#if enableTailwind}} text-emerald-700 font-semibold{{/if}}">
124
- {t('home.description.intro')} <code className="code">presetUltramodern(...)</code>{' '}
125
- {/* i18n-ignore technical token */}
124
+ {t('home.description.intro')}{' '}
125
+ <code className="code">presetUltramodern(...)</code>{' '}
126
126
  {t('home.description.afterPreset')}{' '}
127
- <code className="code">modern.config.ts</code>
128
- {/* i18n-ignore technical token */}
129
- {' '}
127
+ <code className="code">modern.config.ts</code>{' '}
130
128
  {t('home.description.afterConfig')}{' '}
131
- <code className="code">pnpm run ultramodern:check</code>
132
- {/* i18n-ignore technical token */}
133
- {' '}
129
+ <code className="code">pnpm run ultramodern:check</code>{' '}
134
130
  {t('home.description.end')}
135
131
  </p>
136
132
  {{#if useEffectBff}}
@@ -2,9 +2,9 @@
2
2
 
3
3
  export default {
4
4
  content: ['./src/**/*.{js,ts,jsx,tsx}'],
5
+ plugins: [],
5
6
  theme: {
6
7
  extend: {},
7
8
  },
8
- plugins: [],
9
9
  } satisfies Config;
10
10
  {{/if}}
@@ -28,7 +28,9 @@ Run the scaffold validator before adding business code and after every
28
28
 
29
29
  ```bash
30
30
  mise install
31
- pnpm ultramodern:check
31
+ pnpm install
32
+ pnpm check
33
+ pnpm build
32
34
  ```
33
35
 
34
36
  By default, `pnpm install` also prepares read-only agent reference repositories
@@ -50,3 +52,28 @@ UltraModern.js runtime and tooling packages on `workspace:*` for monorepo
50
52
  development. To create a workspace that can install those packages outside the
51
53
  monorepo, generate with `--ultramodern-package-source install`; generated shared
52
54
  packages still use `workspace:*` because they are part of this workspace.
55
+
56
+ ## Cloudflare Proof
57
+
58
+ Deploy the generated apps, then pass each deployed app's generated public URL
59
+ env key into the proof step. A shell-only workspace only needs the shell URL;
60
+ added verticals use the same `ULTRAMODERN_PUBLIC_URL_<APP_ID>` pattern with
61
+ hyphens converted to underscores and uppercased.
62
+
63
+ ```bash
64
+ pnpm cloudflare:deploy
65
+ ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP=https://shell-super-app.example.workers.dev \
66
+ pnpm cloudflare:proof -- --require-public-urls
67
+ ```
68
+
69
+ ## Troubleshooting
70
+
71
+ | Symptom | Current check | Owner |
72
+ | --- | --- | --- |
73
+ | Package cohort mismatch | Regenerate with one package source strategy, run `mise install`, then rerun `pnpm install` from the activated shell. | Generated workspace package source metadata |
74
+ | Install failure | Check the active Node/pnpm from `mise install`; rerun `pnpm install` after the shell sees the pinned versions. | Toolchain setup |
75
+ | Build failure | Run `pnpm check` before `pnpm build`; fix reported format, lint, type, skill, i18n, or generated-contract failures first. | Owning package or generated contract |
76
+ | Missing public URL | Set the env key from `.modernjs/ultramodern-generated-contract.json`, for example `ULTRAMODERN_PUBLIC_URL_SHELL_SUPER_APP`. | Deployment operator |
77
+ | Cloudflare credentials | Confirm Wrangler credentials before `pnpm cloudflare:deploy`; local checks do not prove live Worker access. | Deployment operator |
78
+ | Asset or CSS 404 | Rebuild with `pnpm build` or `pnpm cloudflare:deploy` and inspect emitted Modern/Rspack asset paths instead of hardcoding CSS URLs. | Framework/runtime asset pipeline |
79
+ | Federation manifest failure | Run the shell and vertical build scripts, then check each deployed `/mf-manifest.json` URL used by the shell. | Module Federation owner |
@@ -3,7 +3,13 @@ packages:
3
3
  - verticals/*
4
4
  - packages/*
5
5
 
6
- minimumReleaseAge: 0
6
+ minimumReleaseAge: 1440
7
+ minimumReleaseAgeStrict: true
8
+ minimumReleaseAgeIgnoreMissingTime: false
9
+ minimumReleaseAgeExclude:
10
+ - '@bleedingdev/modern-js-*'
11
+ trustPolicy: no-downgrade
12
+ trustPolicyIgnoreAfter: 1440
7
13
  blockExoticSubdeps: true
8
14
  engineStrict: true
9
15
  pmOnFail: error