@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 +27 -23
- package/dist/index.js +41 -49
- package/package.json +3 -3
- package/template-workspace/README.md.handlebars +2 -2
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
|
|
16
|
-
|
|
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-
|
|
21
|
+
pnpm dlx @bleedingdev/modern-js-create my-app
|
|
20
22
|
```
|
|
21
23
|
|
|
22
|
-
|
|
23
|
-
|
|
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
|
-
|
|
57
|
+
TanStack Router is generated by default. To force the compatibility router:
|
|
49
58
|
|
|
50
59
|
```bash
|
|
51
|
-
|
|
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
|
-
|
|
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
|
-
|
|
75
|
+
pnpm dlx @bleedingdev/modern-js-create my-app
|
|
67
76
|
```
|
|
68
77
|
|
|
69
78
|
### BFF Runtime Template
|
|
70
79
|
|
|
71
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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 --
|
|
488
|
-
example9: ' create my-app --
|
|
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
|
|
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 --
|
|
546
|
-
example9: ' create my-app --
|
|
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
|
-
<
|
|
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
|
-
</
|
|
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(
|
|
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
|
-
|
|
4530
|
+
const fail = (message) => {
|
|
4533
4531
|
throw new Error(message);
|
|
4534
|
-
}
|
|
4532
|
+
};
|
|
4535
4533
|
|
|
4536
|
-
|
|
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
|
-
|
|
4555
|
-
return path.relative(root, filePath).replace(/\\\\/gu, '/');
|
|
4556
|
-
}
|
|
4552
|
+
const relative = (filePath) => path.relative(root, filePath).replaceAll('\\\\', '/');
|
|
4557
4553
|
|
|
4558
|
-
|
|
4559
|
-
return /\\.(?:ts|tsx|js|jsx)$/u.test(filePath);
|
|
4560
|
-
}
|
|
4554
|
+
const isSourceFile = (filePath) => /\\.(?:ts|tsx|js|jsx)$/u.test(filePath);
|
|
4561
4555
|
|
|
4562
|
-
|
|
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
|
-
|
|
4568
|
-
return fs.readFileSync(filePath, 'utf8');
|
|
4569
|
-
}
|
|
4561
|
+
const readText = (filePath) => fs.readFileSync(filePath, 'utf-8');
|
|
4570
4562
|
|
|
4571
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
18
|
-
pnpm
|
|
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,
|