@bleedingdev/modern-js-create 3.2.0-ultramodern.73 → 3.2.0-ultramodern.76

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
@@ -12,15 +12,24 @@
12
12
 
13
13
  Please follow [Quick Start](https://modernjs.dev/en/guides/get-started/quick-start) to get started with Modern.js.
14
14
 
15
- For UltraModern.js, use the BleedingDev create package. It defaults to the
16
- canonical Tractor SuperApp workspace and published BleedingDev package aliases:
15
+ For UltraModern.js, use the BleedingDev create package. It defaults to a
16
+ production-ready single app with `presetUltramodern(...)`, TanStack Router,
17
+ Tailwind CSS v4, i18n, Effect BFF, generated quality gates, and published
18
+ BleedingDev package aliases:
17
19
 
18
20
  ```bash
19
- pnpm dlx @bleedingdev/modern-js-create my-super-app
21
+ pnpm dlx @bleedingdev/modern-js-create my-app
20
22
  ```
21
23
 
22
- The default workspace is a full-stack reference, not a visual-only commerce
23
- boundary demo. It generates:
24
+ Create a full SuperApp workspace only when you need independently owned
25
+ verticals:
26
+
27
+ ```bash
28
+ pnpm dlx @bleedingdev/modern-js-create my-super-app --ultramodern-workspace
29
+ ```
30
+
31
+ The workspace is a full-stack reference, not a visual-only commerce boundary
32
+ demo. It generates:
24
33
 
25
34
  - `apps/shell-super-app` as the Module Federation host and topology owner.
26
35
  - `verticals/explore` for discovery UI plus
@@ -45,10 +54,10 @@ pnpm build
45
54
 
46
55
  ### Router Template
47
56
 
48
- You can scaffold a TanStack Router first template:
57
+ TanStack Router is generated by default. To force the compatibility router:
49
58
 
50
59
  ```bash
51
- npx @modern-js/create my-app --router tanstack
60
+ pnpm dlx @bleedingdev/modern-js-create my-app --router react-router
52
61
  ```
53
62
 
54
63
  ### Tailwind Template
@@ -57,43 +66,38 @@ Tailwind CSS v4 setup is generated by default. Disable it explicitly when you
57
66
  need a plain CSS starter:
58
67
 
59
68
  ```bash
60
- npx @modern-js/create my-app --no-tailwind
69
+ pnpm dlx @bleedingdev/modern-js-create my-app --no-tailwind
61
70
  ```
62
71
 
63
72
  TanStack Router and Tailwind CSS work together without extra flags:
64
73
 
65
74
  ```bash
66
- npx @modern-js/create my-app --router tanstack
75
+ pnpm dlx @bleedingdev/modern-js-create my-app
67
76
  ```
68
77
 
69
78
  ### BFF Runtime Template
70
79
 
71
- You can scaffold BFF APIs with the current default runtime:
72
-
73
- ```bash
74
- npx @modern-js/create my-app --bff
75
- ```
76
-
77
- You can explicitly scaffold Effect HttpApi runtime for BFF:
80
+ UltraModern app scaffolds include Effect HttpApi BFF by default:
78
81
 
79
82
  ```bash
80
- npx @modern-js/create my-app --bff-runtime effect
83
+ pnpm dlx @bleedingdev/modern-js-create my-app
81
84
  ```
82
85
 
83
86
  To scaffold Hono runtime explicitly:
84
87
 
85
88
  ```bash
86
- npx @modern-js/create my-app --bff-runtime hono
89
+ pnpm dlx @bleedingdev/modern-js-create my-app --bff-runtime hono
87
90
  ```
88
91
 
89
92
  Generated starters expose `presetUltramodern(...)` as the public opinionated
90
93
  config wrapper when you want the full Ultramodern setup surface in
91
94
  `modern.config.ts`.
92
95
 
93
- You can combine TanStack Router + default Tailwind + Effect BFF in one command:
96
+ TanStack Router, default Tailwind, and Effect BFF are included without extra
97
+ flags. For local monorepo dependency testing, add `--workspace`:
94
98
 
95
99
  ```bash
96
- npx @modern-js/create my-app --router tanstack --bff-runtime effect
100
+ pnpm dlx @bleedingdev/modern-js-create my-app --workspace
97
101
  ```
98
102
 
99
103
  ### Vertical Workspace Recipes
@@ -104,7 +108,7 @@ overlay, ownership entry, Effect BFF surface, and root `dev:*` script from the
104
108
  requested vertical name.
105
109
 
106
110
  ```bash
107
- npx @modern-js/create catalog --vertical
111
+ pnpm dlx @bleedingdev/modern-js-create catalog --vertical
108
112
  ```
109
113
 
110
114
  Use this decision table before adding a vertical:
@@ -195,14 +199,14 @@ When testing unreleased Modern.js packages from a local monorepo checkout, use
195
199
  workspace protocol dependencies:
196
200
 
197
201
  ```bash
198
- npx @modern-js/create my-app --router tanstack --bff-runtime effect --workspace
202
+ pnpm dlx @bleedingdev/modern-js-create my-app --workspace
199
203
  ```
200
204
 
201
205
  For package-source validation of the full Tractor workspace, generate with the
202
206
  workspace package source, then run the generated contract gate:
203
207
 
204
208
  ```bash
205
- npx @modern-js/create tractor-super-app --workspace
209
+ pnpm dlx @bleedingdev/modern-js-create tractor-super-app --ultramodern-workspace --ultramodern-package-source workspace
206
210
  cd tractor-super-app
207
211
  pnpm install
208
212
  pnpm ultramodern:check
package/dist/index.js CHANGED
@@ -466,7 +466,7 @@ const EN_LOCALE = {
466
466
  optionVersion: ' -v, --version Display version information',
467
467
  optionLang: ' -l, --lang Set the language (zh or en)',
468
468
  optionRouter: ' -r, --router Select router framework (react-router or tanstack)',
469
- optionBff: ' --bff Enable BFF scaffold (default runtime: effect)',
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)',
@@ -483,9 +483,9 @@ const EN_LOCALE = {
483
483
  example4: ' create --help',
484
484
  example5: ' create my-app --router tanstack',
485
485
  example6: ' create my-app --router tanstack --no-tailwind',
486
- example7: ' create my-app --bff',
487
- example8: ' create my-app --router tanstack --bff-runtime effect',
488
- example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
486
+ example7: ' create my-app --bff-runtime hono',
487
+ example8: ' create my-app --workspace',
488
+ example9: ' create my-app --bff-runtime effect --workspace',
489
489
  example10: ' pnpm dlx @bleedingdev/modern-js-create my-app',
490
490
  example11: ' pnpm dlx @bleedingdev/modern-js-create my-super-app --ultramodern-workspace',
491
491
  example12: ' create catalog --vertical',
@@ -524,7 +524,7 @@ const ZH_LOCALE = {
524
524
  optionVersion: ' -v, --version 显示版本信息',
525
525
  optionLang: ' -l, --lang 设置语言 (zh 或 en)',
526
526
  optionRouter: ' -r, --router 选择路由框架 (react-router 或 tanstack)',
527
- optionBff: ' --bff 启用 BFF 模板(默认运行时:effect)',
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 联调)',
@@ -541,9 +541,9 @@ const ZH_LOCALE = {
541
541
  example4: ' create --help',
542
542
  example5: ' create my-app --router tanstack',
543
543
  example6: ' create my-app --router tanstack --no-tailwind',
544
- example7: ' create my-app --bff',
545
- example8: ' create my-app --router tanstack --bff-runtime effect',
546
- example9: ' create my-app --router tanstack --bff-runtime effect --workspace',
544
+ example7: ' create my-app --bff-runtime hono',
545
+ example8: ' create my-app --workspace',
546
+ example9: ' create my-app --bff-runtime effect --workspace',
547
547
  example10: ' pnpm dlx @bleedingdev/modern-js-create my-app',
548
548
  example11: ' pnpm dlx @bleedingdev/modern-js-create my-super-app --ultramodern-workspace',
549
549
  example12: ' create catalog --vertical',
@@ -2482,7 +2482,7 @@ function createRemotePage(app) {
2482
2482
  const listEffectItems = `list${toPascalCase(effectApiStem(app))}`;
2483
2483
  const effectBffImport = appHasEffectApi(app) ? `import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
2484
2484
  import { Helmet } from '@modern-js/runtime/head';
2485
- import { useLocation } from '@modern-js/plugin-tanstack/runtime';
2485
+ import { Link, useLocation } from '@modern-js/plugin-tanstack/runtime';
2486
2486
  import { useEffect, useState } from 'react';
2487
2487
  import {
2488
2488
  Effect,
@@ -2491,7 +2491,7 @@ import {
2491
2491
  } from '../../effect/${effectApiStem(app)}-client';
2492
2492
  import { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';
2493
2493
  import { ultramodernUiMarker } from '../../ultramodern-build';
2494
- ` : "import { useModernI18n } from '@modern-js/plugin-i18n/runtime';\nimport { Helmet } from '@modern-js/runtime/head';\nimport { useLocation } from '@modern-js/plugin-tanstack/runtime';\nimport { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';\nimport { ultramodernUiMarker } from '../../ultramodern-build';\n";
2494
+ ` : "import { useModernI18n } from '@modern-js/plugin-i18n/runtime';\nimport { Helmet } from '@modern-js/runtime/head';\nimport { Link, useLocation } from '@modern-js/plugin-tanstack/runtime';\nimport { ultramodernLocalisedUrls } from '../ultramodern-route-metadata';\nimport { ultramodernUiMarker } from '../../ultramodern-build';\n";
2495
2495
  const effectBffState = appHasEffectApi(app) ? ` const [effectApiStatus, setEffectApiStatus] = useState('pending');
2496
2496
 
2497
2497
  useEffect(() => {
@@ -2528,21 +2528,20 @@ ${createLocalizedHeadComponent()}
2528
2528
  export default function ${toPascalCase(app.id)}Home() {
2529
2529
  const { i18nInstance, language } = useModernI18n();
2530
2530
  const t = i18nInstance['t'].bind(i18nInstance);
2531
- const location = useLocation();
2532
- const suffix = locationSuffix(location);
2533
2531
  ${effectBffState} return (
2534
2532
  <main className="${tw('min-h-screen bg-um-canvas px-4 py-6 text-um-foreground sm:px-8')}">
2535
2533
  <LocalizedHead />
2536
2534
  <nav aria-label={t('${app.domain}.language.switcher')} className="${tw('flex gap-3')}">
2537
2535
  {supportedLanguages.map(code => (
2538
- <a
2536
+ <Link
2539
2537
  aria-current={language === code ? 'page' : undefined}
2540
2538
  className="${tw('rounded-full border border-stone-900/15 bg-white px-4 py-2 text-sm font-bold text-stone-950 no-underline')}"
2541
- href={\`\${localizedPath(location.pathname, code)}\${suffix}\`}
2542
2539
  key={code}
2540
+ params={{ lang: code }}
2541
+ to="/$lang"
2543
2542
  >
2544
2543
  {t(\`${app.domain}.language.\${code}\`)}
2545
- </a>
2544
+ </Link>
2546
2545
  ))}
2547
2546
  </nav>
2548
2547
  <h1 className="${tw('mt-10 text-5xl font-black')}">{t('${app.domain}.title')}</h1>
@@ -4507,9 +4506,8 @@ function createWorkspaceI18nBoundaryValidationScript() {
4507
4506
  return `#!/usr/bin/env node
4508
4507
  import fs from 'node:fs';
4509
4508
  import path from 'node:path';
4510
- import { fileURLToPath } from 'node:url';
4511
4509
 
4512
- const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '..');
4510
+ const root = path.resolve(import.meta.dirname, '..');
4513
4511
  const sourceRoots = ['apps', 'verticals'];
4514
4512
  const languageConditionalPattern =
4515
4513
  /\\b(language|locale|lng|currentLanguage)\\s*={0,2}={1,2}\\s*['"][a-z-]+['"]\\s*\\?\\s*([^:;\\n]+)\\s*:\\s*([^;\\n})]+)/gu;
@@ -4529,11 +4527,11 @@ const visibleCopyAttributes = new Set([
4529
4527
  'title',
4530
4528
  ]);
4531
4529
 
4532
- function fail(message) {
4530
+ const fail = (message) => {
4533
4531
  throw new Error(message);
4534
- }
4532
+ };
4535
4533
 
4536
- function walk(directory, files = []) {
4534
+ const walk = (directory, files = []) => {
4537
4535
  if (!fs.existsSync(directory)) {
4538
4536
  return files;
4539
4537
  }
@@ -4549,43 +4547,37 @@ function walk(directory, files = []) {
4549
4547
  }
4550
4548
  }
4551
4549
  return files;
4552
- }
4550
+ };
4553
4551
 
4554
- function relative(filePath) {
4555
- return path.relative(root, filePath).replace(/\\\\/gu, '/');
4556
- }
4552
+ const relative = (filePath) => path.relative(root, filePath).replaceAll('\\\\', '/');
4557
4553
 
4558
- function isSourceFile(filePath) {
4559
- return /\\.(?:ts|tsx|js|jsx)$/u.test(filePath);
4560
- }
4554
+ const isSourceFile = (filePath) => /\\.(?:ts|tsx|js|jsx)$/u.test(filePath);
4561
4555
 
4562
- function isLocaleJson(filePath) {
4556
+ const isLocaleJson = (filePath) => {
4563
4557
  const normalized = relative(filePath);
4564
4558
  return /\\/locales\\/(en|cs)\\/[^/]+\\.json$/u.test(normalized);
4565
- }
4559
+ };
4566
4560
 
4567
- function readText(filePath) {
4568
- return fs.readFileSync(filePath, 'utf8');
4569
- }
4561
+ const readText = (filePath) => fs.readFileSync(filePath, 'utf-8');
4570
4562
 
4571
- function branchIsUserCopy(branch) {
4563
+ const branchIsUserCopy = (branch) => {
4572
4564
  const value = branch.trim().replace(/,$/u, '');
4573
4565
  if (allowedLanguageConditionalBranches.has(value)) {
4574
4566
  return false;
4575
4567
  }
4576
4568
  return /^['"][^'"]{2,}['"]$/u.test(value);
4577
- }
4569
+ };
4578
4570
 
4579
- function checkRuntimeResources(filePath, text) {
4571
+ const checkRuntimeResources = (filePath, text) => {
4580
4572
  if (!relative(filePath).endsWith('/src/modern.runtime.ts')) {
4581
4573
  return;
4582
4574
  }
4583
4575
  if (/initOptions\\s*:\\s*\\{[\\s\\S]*?\\bresources\\s*:/u.test(text)) {
4584
4576
  fail(\`\${relative(filePath)} must not inline i18n resources in modern.runtime.ts; use locale JSON files.\`);
4585
4577
  }
4586
- }
4578
+ };
4587
4579
 
4588
- function checkLanguageConditionals(filePath, text) {
4580
+ const checkLanguageConditionals = (filePath, text) => {
4589
4581
  for (const match of text.matchAll(languageConditionalPattern)) {
4590
4582
  const [, name, whenTrue = '', whenFalse = ''] = match;
4591
4583
  if (branchIsUserCopy(whenTrue) || branchIsUserCopy(whenFalse)) {
@@ -4594,9 +4586,9 @@ function checkLanguageConditionals(filePath, text) {
4594
4586
  );
4595
4587
  }
4596
4588
  }
4597
- }
4589
+ };
4598
4590
 
4599
- function checkLiteralVisibleAttributes(filePath, text) {
4591
+ const checkLiteralVisibleAttributes = (filePath, text) => {
4600
4592
  if (!filePath.endsWith('.tsx') && !filePath.endsWith('.jsx')) {
4601
4593
  return;
4602
4594
  }
@@ -4608,17 +4600,17 @@ function checkLiteralVisibleAttributes(filePath, text) {
4608
4600
  );
4609
4601
  }
4610
4602
  }
4611
- }
4603
+ };
4612
4604
 
4613
- function checkSplitPhraseKeys(filePath, text) {
4605
+ const checkSplitPhraseKeys = (filePath, text) => {
4614
4606
  if (/t\\(\\s*['"][^'"]+\\.(?:prefix|suffix|before|after)['"]\\s*\\)/u.test(text)) {
4615
4607
  fail(
4616
4608
  \`\${relative(filePath)} uses split phrase translation keys. Keep translator-owned phrases whole.\`,
4617
4609
  );
4618
4610
  }
4619
- }
4611
+ };
4620
4612
 
4621
- function checkBoundaryAttributes(filePath, text) {
4613
+ const checkBoundaryAttributes = (filePath, text) => {
4622
4614
  if (!filePath.endsWith('.tsx') && !filePath.endsWith('.jsx')) {
4623
4615
  return;
4624
4616
  }
@@ -4627,9 +4619,9 @@ function checkBoundaryAttributes(filePath, text) {
4627
4619
  \`\${relative(filePath)} uses legacy data-mf-* boundary attributes. Use data-modern-boundary-id and data-modern-mf-expose.\`,
4628
4620
  );
4629
4621
  }
4630
- }
4622
+ };
4631
4623
 
4632
- function visitLocaleKeys(value, visitor, pathParts = []) {
4624
+ const visitLocaleKeys = (value, visitor, pathParts = []) => {
4633
4625
  if (!value || typeof value !== 'object' || Array.isArray(value)) {
4634
4626
  return;
4635
4627
  }
@@ -4638,9 +4630,9 @@ function visitLocaleKeys(value, visitor, pathParts = []) {
4638
4630
  visitor(key, child, nextPath);
4639
4631
  visitLocaleKeys(child, visitor, nextPath);
4640
4632
  }
4641
- }
4633
+ };
4642
4634
 
4643
- function checkPluralResources(filePath, json) {
4635
+ const checkPluralResources = (filePath, json) => {
4644
4636
  const language = relative(filePath).split('/locales/')[1]?.split('/')[0];
4645
4637
  const requiredSuffixes =
4646
4638
  language === 'cs' ? ['one', 'few', 'many', 'other'] : ['one', 'other'];
@@ -4670,7 +4662,7 @@ function checkPluralResources(filePath, json) {
4670
4662
  }
4671
4663
  }
4672
4664
  }
4673
- }
4665
+ };
4674
4666
 
4675
4667
  const sourceFiles = sourceRoots.flatMap(sourceRoot =>
4676
4668
  walk(path.join(root, sourceRoot)).filter(filePath => isSourceFile(filePath)),
@@ -5759,7 +5751,7 @@ function detectBffRuntime() {
5759
5751
  const runtimeValue = getOptionValue(args, [
5760
5752
  '--bff-runtime'
5761
5753
  ]);
5762
- if (!runtimeValue) return args.includes('--bff') ? 'effect' : 'none';
5754
+ if (!runtimeValue) return 'effect';
5763
5755
  if ('hono' === runtimeValue || 'effect' === runtimeValue) return runtimeValue;
5764
5756
  console.error(i18n.t(localeKeys.error.invalidBffRuntime, {
5765
5757
  runtime: runtimeValue
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
24
- "version": "3.2.0-ultramodern.73",
24
+ "version": "3.2.0-ultramodern.76",
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.73"
44
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.76"
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.73"
57
+ "frameworkVersion": "3.2.0-ultramodern.76"
58
58
  }
59
59
  }
@@ -14,8 +14,8 @@ UltraModern.js 3.0 SuperApp surface and starts with an explicit shell:
14
14
  Add a full-stack MicroVertical when the product needs one:
15
15
 
16
16
  ```bash
17
- pnpm create modern@latest transportation --vertical
18
- pnpm create modern@latest payments --vertical
17
+ pnpm dlx @bleedingdev/modern-js-create transportation --vertical
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,