@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 +46 -37
- package/dist/index.js +52 -28
- package/package.json +3 -3
- package/template/AGENTS.md +1 -1
- package/template/oxfmt.config.ts +8 -1
- package/template/oxlint.config.ts +8 -1
- package/template/package.json.handlebars +2 -2
- package/template/pnpm-workspace.yaml +10 -0
- package/template/rstest.config.mts +1 -3
- package/template/scripts/bootstrap-agent-skills.mjs +74 -0
- package/template/scripts/check-i18n-strings.mjs +12 -1
- package/template/scripts/validate-ultramodern.mjs.handlebars +37 -23
- package/template/src/routes/[lang]/page.tsx.handlebars +4 -8
- package/template/tailwind.config.ts.handlebars +1 -1
- package/template-workspace/README.md.handlebars +28 -1
- package/template-workspace/pnpm-workspace.yaml +7 -1
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
|
-
|
|
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
|
|
38
|
-
|
|
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
|
|
42
|
-
|
|
43
|
-
|
|
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
|
|
53
|
+
pnpm check
|
|
58
54
|
pnpm build
|
|
59
55
|
```
|
|
60
56
|
|
|
61
57
|
### Router Template
|
|
62
58
|
|
|
63
|
-
TanStack Router is generated by default.
|
|
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
|
|
131
|
-
for
|
|
132
|
-
`--vertical` so paths, topology, Effect BFF
|
|
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
|
-
###
|
|
141
|
+
### SuperApp Architecture Contracts
|
|
145
142
|
|
|
146
|
-
The generated shell owns route assembly and policy.
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
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
|
|
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
|
|
216
|
-
cd
|
|
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
|
|
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
|
|
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 (
|
|
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
|
|
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
|
|
1914
|
-
if (!
|
|
1915
|
-
return
|
|
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
|
|
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
|
-
${
|
|
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.
|
|
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.
|
|
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.
|
|
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(
|
|
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.
|
|
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.
|
|
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.
|
|
57
|
+
"frameworkVersion": "3.2.0-ultramodern.84"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/template/AGENTS.md
CHANGED
|
@@ -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
|
|
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
|
|
package/template/oxfmt.config.ts
CHANGED
|
@@ -3,6 +3,13 @@ import ultracite from 'ultracite/oxfmt';
|
|
|
3
3
|
|
|
4
4
|
export default defineConfig({
|
|
5
5
|
extends: [ultracite],
|
|
6
|
-
ignorePatterns: [
|
|
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: [
|
|
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
|
|
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.
|
|
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
|
|
@@ -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 (
|
|
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 =
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
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 (
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
fs.existsSync(path.resolve(process.cwd(), 'tailwind.config.ts'))
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
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 (
|
|
334
|
-
|
|
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 (
|
|
370
|
-
|
|
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')}
|
|
125
|
-
{
|
|
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}}
|
|
@@ -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
|
|
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:
|
|
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
|