@bleedingdev/modern-js-create 3.2.0-ultramodern.21 → 3.2.0-ultramodern.23

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.
@@ -35,6 +35,7 @@ export declare const EN_LOCALE: {
35
35
  optionUltramodernPackageSource: string;
36
36
  optionUltramodernPackageScope: string;
37
37
  optionUltramodernPackageNamePrefix: string;
38
+ optionMicroVertical: string;
38
39
  optionSub: string;
39
40
  examples: string;
40
41
  example1: string;
@@ -47,6 +48,7 @@ export declare const EN_LOCALE: {
47
48
  example8: string;
48
49
  example9: string;
49
50
  example10: string;
51
+ example11: string;
50
52
  moreInfo: string;
51
53
  };
52
54
  version: {
@@ -35,6 +35,7 @@ export declare const ZH_LOCALE: {
35
35
  optionUltramodernPackageSource: string;
36
36
  optionUltramodernPackageScope: string;
37
37
  optionUltramodernPackageNamePrefix: string;
38
+ optionMicroVertical: string;
38
39
  optionSub: string;
39
40
  examples: string;
40
41
  example1: string;
@@ -47,6 +48,7 @@ export declare const ZH_LOCALE: {
47
48
  example8: string;
48
49
  example9: string;
49
50
  example10: string;
51
+ example11: string;
50
52
  moreInfo: string;
51
53
  };
52
54
  version: {
@@ -1,8 +1,10 @@
1
+ export type MicroVerticalKind = 'remote' | 'horizontal-remote' | 'service' | 'shared';
1
2
  type UltramodernPackageSourceStrategy = 'workspace' | 'install';
2
3
  export type UltramodernWorkspaceOptions = {
3
4
  targetDir: string;
4
5
  packageName: string;
5
6
  modernVersion: string;
7
+ enableTailwind?: boolean;
6
8
  packageSource?: {
7
9
  strategy?: UltramodernPackageSourceStrategy;
8
10
  modernPackageVersion?: string;
@@ -11,10 +13,21 @@ export type UltramodernWorkspaceOptions = {
11
13
  aliasPackageNamePrefix?: string;
12
14
  };
13
15
  };
16
+ export type AddUltramodernMicroVerticalOptions = {
17
+ workspaceRoot: string;
18
+ name: string;
19
+ kind: MicroVerticalKind;
20
+ modernVersion: string;
21
+ enableTailwind?: boolean;
22
+ packageSource?: UltramodernWorkspaceOptions['packageSource'];
23
+ };
14
24
  export declare const ULTRAMODERN_WORKSPACE_FLAG = "--ultramodern-workspace";
25
+ export declare function addUltramodernMicroVertical(options: AddUltramodernMicroVerticalOptions): void;
15
26
  export declare function generateUltramodernWorkspace(options: UltramodernWorkspaceOptions): void;
16
27
  export declare const ultramodernWorkspaceVersions: {
17
28
  tanstackRouter: string;
18
29
  moduleFederation: string;
30
+ tailwind: string;
31
+ tailwindPostcss: string;
19
32
  };
20
33
  export {};
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
24
- "version": "3.2.0-ultramodern.21",
24
+ "version": "3.2.0-ultramodern.23",
25
25
  "types": "./dist/types/index.d.ts",
26
26
  "main": "./dist/index.js",
27
27
  "bin": {
@@ -38,10 +38,10 @@
38
38
  ],
39
39
  "devDependencies": {
40
40
  "@rslib/core": "0.21.5",
41
- "@types/node": "^25.8.0",
42
- "@typescript/native-preview": "7.0.0-dev.20260516.1",
43
- "tsx": "^4.22.0",
44
- "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.17"
41
+ "@types/node": "^25.9.1",
42
+ "@typescript/native-preview": "7.0.0-dev.20260526.1",
43
+ "tsx": "^4.22.3",
44
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.23"
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.17"
57
+ "frameworkVersion": "3.2.0-ultramodern.23"
58
58
  }
59
59
  }
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ pnpm = "11.3.0"
@@ -46,14 +46,15 @@ major upgrades.
46
46
 
47
47
  ## Micro Vertical Workspaces
48
48
 
49
- Inside a Micro Vertical workspace, generate shell, remote, and service packages
50
- with `--sub` so the workspace root owns package-manager and CI policy:
49
+ Inside a Micro Vertical workspace, add packages from the workspace root with
50
+ the UltraModern add flow. It derives paths, package names, ports, Module
51
+ Federation names, topology entries, overlays, ownership, and root dev scripts:
51
52
 
52
53
  ```bash
53
- npx @modern-js/create apps/shell --router tanstack --tailwind --workspace --sub
54
- npx @modern-js/create apps/remotes/catalog --router tanstack --tailwind --workspace --sub
55
- npx @modern-js/create apps/remotes/design-system --router tanstack --tailwind --workspace --sub
56
- npx @modern-js/create services/catalog-api --bff-runtime effect --workspace --sub
54
+ npx @modern-js/create catalog --microvertical remote
55
+ npx @modern-js/create design-system --microvertical horizontal-remote
56
+ npx @modern-js/create catalog-api --microvertical service
57
+ npx @modern-js/create catalog-contracts --microvertical shared
57
58
  ```
58
59
 
59
60
  The canonical topology is documented in
@@ -3,7 +3,7 @@
3
3
  "version": "0.1.0",
4
4
  "private": true,
5
5
  "type": "module",
6
- "packageManager": "pnpm@11.1.2",
6
+ "packageManager": "pnpm@11.3.0",
7
7
  "scripts": {
8
8
  "reset": "npx rimraf node_modules ./**/node_modules",
9
9
  "dev": "modern dev",
@@ -28,36 +28,36 @@
28
28
  {{#if isTanstackRouter}} "@modern-js/plugin-tanstack": "{{pluginTanstackVersion}}",
29
29
  {{/if}}
30
30
  "@modern-js/runtime": "{{runtimeVersion}}",
31
- {{#if isTanstackRouter}} "@tanstack/react-router": "1.170.1",
31
+ {{#if isTanstackRouter}} "@tanstack/react-router": "{{tanstackRouterVersion}}",
32
32
  {{/if}}
33
33
  "i18next": "26.2.0",
34
- "react": "^19.2.3",
35
- "react-dom": "^19.2.0",
34
+ "react": "^19.2.6",
35
+ "react-dom": "^19.2.6",
36
36
  "react-i18next": "17.0.8"
37
37
  },
38
38
  "devDependencies": {
39
- "@effect/tsgo": "0.7.3",
39
+ "@effect/tsgo": "0.11.0",
40
40
  "@modern-js/adapter-rstest": "{{adapterRstestVersion}}",
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.0",
44
+ "@rstest/core": "0.10.2",
45
45
  {{#if enableTailwind}}
46
- "@tailwindcss/postcss": "^4.1.18",
46
+ "@tailwindcss/postcss": "^{{tailwindPostcssVersion}}",
47
47
  {{/if}}
48
48
  "@types/node": "^20",
49
49
  "@types/react": "^19.1.8",
50
50
  "@types/react-dom": "^19.1.6",
51
- "@typescript/native-preview": "7.0.0-dev.20260518.1",
52
- "happy-dom": "^20.8.9",
51
+ "@typescript/native-preview": "7.0.0-dev.20260526.1",
52
+ "happy-dom": "^20.9.0",
53
53
  {{#unless isSubproject}}
54
- "lint-staged": "~15.4.0",
55
- "oxfmt": "0.50.0",
56
- "oxlint": "1.65.0",
54
+ "lint-staged": "~17.0.5",
55
+ "oxfmt": "0.51.0",
56
+ "oxlint": "1.66.0",
57
57
  {{/unless}}{{#if enableTailwind}} "postcss": "^8.5.6",
58
- {{/if}} "rimraf": "^6.0.1"{{#unless isSubproject}},
59
- "simple-git-hooks": "^2.11.1"{{/unless}}{{#if enableTailwind}},
60
- "tailwindcss": "^4.1.18"{{/if}}{{#unless isSubproject}},
58
+ {{/if}} "rimraf": "^6.1.3"{{#unless isSubproject}},
59
+ "simple-git-hooks": "^2.13.1"{{/unless}}{{#if enableTailwind}},
60
+ "tailwindcss": "^{{tailwindVersion}}"{{/if}}{{#unless isSubproject}},
61
61
  "ultracite": "7.7.0"{{/unless}}
62
62
  }{{#unless isSubproject}},
63
63
  "simple-git-hooks": {
@@ -71,6 +71,6 @@
71
71
  }{{/unless}},
72
72
  "engines": {
73
73
  "node": ">=20",
74
- "pnpm": ">=11.0.0"
74
+ "pnpm": ">=11.3.0 <11.4.0"
75
75
  }
76
76
  }
@@ -21,4 +21,6 @@ allowBuilds:
21
21
  core-js: true
22
22
  esbuild: true
23
23
  msgpackr-extract: true
24
+ sharp: true
24
25
  simple-git-hooks: true
26
+ workerd: true
@@ -1,3 +1,4 @@
1
+ import { execFileSync } from 'node:child_process';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
 
@@ -7,7 +8,23 @@ const packageSourcePath = path.resolve(
7
8
  process.cwd(),
8
9
  '.modernjs/ultramodern-package-source.json',
9
10
  );
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
+ }
18
+ const output = execFileSync('pnpm', ['config', 'get', key, '--json'], {
19
+ cwd: process.cwd(),
20
+ env,
21
+ encoding: 'utf-8',
22
+ stdio: ['ignore', 'pipe', 'pipe'],
23
+ }).trim();
24
+ return output ? JSON.parse(output) : undefined;
25
+ };
10
26
  const isSubproject = {{isSubproject}};
27
+ const enableTailwind = {{enableTailwind}};
11
28
 
12
29
  if (!fs.existsSync(configPath)) {
13
30
  console.error('modern.config.ts not found');
@@ -68,13 +85,19 @@ const requiredPaths = [
68
85
  'oxfmt.config.ts',
69
86
  'scripts/bootstrap-agent-skills.mjs',
70
87
  {{/unless}}
88
+ '.mise.toml',
71
89
  '.modernjs/ultramodern-package-source.json',
72
90
  'pnpm-workspace.yaml',
73
91
  'rstest.config.mts',
74
92
  'scripts/check-i18n-strings.mjs',
93
+ {{#if enableTailwind}}
94
+ 'postcss.config.mjs',
95
+ 'tailwind.config.ts',
96
+ {{/if}}
75
97
  'config/public/locales/en/translation.json',
76
98
  'config/public/locales/cs/translation.json',
77
99
  'src/modern-app-env.d.ts',
100
+ 'src/routes/index.css',
78
101
  'src/routes/[lang]/page.tsx',
79
102
  'tests/ultramodern.contract.test.ts',
80
103
  ];
@@ -92,6 +115,36 @@ if (fs.existsSync(path.resolve(process.cwd(), 'src/routes/page.tsx'))) {
92
115
  process.exit(1);
93
116
  }
94
117
 
118
+ const routeCss = fs.readFileSync(path.resolve(process.cwd(), 'src/routes/index.css'), 'utf-8');
119
+ if (enableTailwind) {
120
+ if (!routeCss.includes("@import 'tailwindcss';")) {
121
+ console.error('src/routes/index.css must import Tailwind CSS by default');
122
+ process.exit(1);
123
+ }
124
+ const postcssConfig = fs.readFileSync(path.resolve(process.cwd(), 'postcss.config.mjs'), 'utf-8');
125
+ if (!postcssConfig.includes("'@tailwindcss/postcss'")) {
126
+ console.error('postcss.config.mjs must configure @tailwindcss/postcss');
127
+ process.exit(1);
128
+ }
129
+ const tailwindConfig = fs.readFileSync(path.resolve(process.cwd(), 'tailwind.config.ts'), 'utf-8');
130
+ if (!tailwindConfig.includes("content: ['./src/**/*.{js,ts,jsx,tsx}']")) {
131
+ console.error('tailwind.config.ts must scan generated source files');
132
+ process.exit(1);
133
+ }
134
+ } else {
135
+ if (routeCss.includes("@import 'tailwindcss';")) {
136
+ console.error('src/routes/index.css must omit Tailwind CSS when disabled');
137
+ process.exit(1);
138
+ }
139
+ if (
140
+ fs.existsSync(path.resolve(process.cwd(), 'postcss.config.mjs')) ||
141
+ fs.existsSync(path.resolve(process.cwd(), 'tailwind.config.ts'))
142
+ ) {
143
+ console.error('Tailwind config files must not be written when Tailwind is disabled');
144
+ process.exit(1);
145
+ }
146
+ }
147
+
95
148
  {{#unless isSubproject}}
96
149
  const workflowContent = fs.readFileSync(
97
150
  path.resolve(process.cwd(), '.github/workflows/ultramodern-gates.yml'),
@@ -212,10 +265,6 @@ if (manifestErrors.length > 0) {
212
265
  const packageJson = JSON.parse(
213
266
  fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),
214
267
  );
215
- const pnpmWorkspace = fs.readFileSync(
216
- path.resolve(process.cwd(), 'pnpm-workspace.yaml'),
217
- 'utf-8',
218
- );
219
268
  const packageSource = JSON.parse(fs.readFileSync(packageSourcePath, 'utf-8'));
220
269
  const unresolvedTemplateMarker = String.fromCodePoint(123, 123);
221
270
  if (JSON.stringify(packageJson).includes(unresolvedTemplateMarker)) {
@@ -269,13 +318,13 @@ if (packageJson.private !== true) {
269
318
  process.exit(1);
270
319
  }
271
320
 
272
- if (packageJson.packageManager !== 'pnpm@11.1.2') {
273
- console.error('Generated app package must pin pnpm@11.1.2');
321
+ if (packageJson.packageManager !== 'pnpm@11.3.0') {
322
+ console.error('Generated app package must pin pnpm@11.3.0');
274
323
  process.exit(1);
275
324
  }
276
325
 
277
- if (packageJson.engines?.pnpm !== '>=11.0.0') {
278
- console.error('Generated app package must require pnpm >=11.0.0');
326
+ if (packageJson.engines?.pnpm !== '>=11.3.0 <11.4.0') {
327
+ console.error('Generated app package must require pnpm >=11.3.0 <11.4.0');
279
328
  process.exit(1);
280
329
  }
281
330
 
@@ -284,28 +333,66 @@ if (packageJson.pnpm !== undefined) {
284
333
  process.exit(1);
285
334
  }
286
335
 
287
- for (const requiredSnippet of [
288
- 'minimumReleaseAge: 1440',
289
- 'minimumReleaseAgeStrict: true',
290
- 'minimumReleaseAgeIgnoreMissingTime: false',
291
- "minimumReleaseAgeExclude:\n - '@modern-js/*'\n - '@bleedingdev/*'\n - '@effect/tsgo'\n - '@effect/tsgo-*'\n - '@typescript/native-preview'\n - '@typescript/native-preview-*'",
292
- 'trustPolicy: no-downgrade',
293
- 'trustPolicyIgnoreAfter: 1440',
294
- 'blockExoticSubdeps: true',
295
- 'engineStrict: true',
296
- 'pmOnFail: error',
297
- 'verifyDepsBeforeRun: error',
298
- 'strictDepBuilds: true',
299
- "allowBuilds:\n '@swc/core': true\n core-js: true\n esbuild: true\n msgpackr-extract: true\n simple-git-hooks: true",
336
+ if (readPnpmConfig('minimumReleaseAge') !== 1440) {
337
+ console.error('pnpm minimumReleaseAge must be 1440');
338
+ process.exit(1);
339
+ }
340
+ if (readPnpmConfig('minimumReleaseAgeStrict') !== true) {
341
+ console.error('pnpm minimumReleaseAgeStrict must be true');
342
+ process.exit(1);
343
+ }
344
+ if (readPnpmConfig('minimumReleaseAgeIgnoreMissingTime') !== false) {
345
+ console.error('pnpm minimumReleaseAgeIgnoreMissingTime must be false');
346
+ process.exit(1);
347
+ }
348
+ if (
349
+ JSON.stringify(readPnpmConfig('minimumReleaseAgeExclude')) !==
350
+ JSON.stringify([
351
+ '@modern-js/*',
352
+ '@bleedingdev/*',
353
+ '@effect/tsgo',
354
+ '@effect/tsgo-*',
355
+ '@typescript/native-preview',
356
+ '@typescript/native-preview-*',
357
+ ])
358
+ ) {
359
+ console.error('pnpm minimumReleaseAgeExclude must retain framework exceptions');
360
+ process.exit(1);
361
+ }
362
+ if (readPnpmConfig('trustPolicy') !== 'no-downgrade') {
363
+ console.error('pnpm trustPolicy must be no-downgrade');
364
+ process.exit(1);
365
+ }
366
+ for (const [key, expected] of [
367
+ ['trustPolicyIgnoreAfter', 1440],
368
+ ['blockExoticSubdeps', true],
369
+ ['engineStrict', true],
370
+ ['pmOnFail', 'error'],
371
+ ['verifyDepsBeforeRun', 'error'],
372
+ ['strictDepBuilds', true],
300
373
  ]) {
301
- if (!pnpmWorkspace.includes(requiredSnippet)) {
302
- console.error(`pnpm-workspace.yaml must retain ${requiredSnippet.split('\n')[0]}`);
374
+ if (readPnpmConfig(key) !== expected) {
375
+ console.error(`pnpm ${key} must be ${String(expected)}`);
303
376
  process.exit(1);
304
377
  }
305
378
  }
306
-
307
- if (pnpmWorkspace.includes('onlyBuiltDependencies')) {
308
- console.error('pnpm-workspace.yaml must use pnpm 11 allowBuilds instead of onlyBuiltDependencies');
379
+ if (
380
+ JSON.stringify(readPnpmConfig('allowBuilds')) !==
381
+ JSON.stringify({
382
+ '@swc/core': true,
383
+ 'core-js': true,
384
+ esbuild: true,
385
+ 'msgpackr-extract': true,
386
+ sharp: true,
387
+ 'simple-git-hooks': true,
388
+ workerd: true,
389
+ })
390
+ ) {
391
+ console.error('pnpm allowBuilds must approve only the generated app build dependencies');
392
+ process.exit(1);
393
+ }
394
+ if (readPnpmConfig('onlyBuiltDependencies') !== undefined) {
395
+ console.error('pnpm onlyBuiltDependencies must not be set');
309
396
  process.exit(1);
310
397
  }
311
398
 
@@ -408,6 +495,11 @@ for (const dependency of [
408
495
  '@rstest/core',
409
496
  '@typescript/native-preview',
410
497
  'happy-dom',
498
+ {{#if enableTailwind}}
499
+ '@tailwindcss/postcss',
500
+ 'postcss',
501
+ 'tailwindcss',
502
+ {{/if}}
411
503
  {{#unless isSubproject}}
412
504
  'oxlint',
413
505
  'oxfmt',
@@ -420,6 +512,26 @@ for (const dependency of [
420
512
  }
421
513
  }
422
514
 
515
+ {{#if enableTailwind}}
516
+ if (
517
+ packageJson.devDependencies?.tailwindcss !== '^4.3.0' ||
518
+ packageJson.devDependencies?.['@tailwindcss/postcss'] !== '^4.3.0'
519
+ ) {
520
+ console.error('Tailwind CSS dependencies must use the UltraModern default baseline');
521
+ process.exit(1);
522
+ }
523
+ {{/if}}
524
+ {{#unless enableTailwind}}
525
+ if (
526
+ packageJson.devDependencies?.tailwindcss ||
527
+ packageJson.devDependencies?.['@tailwindcss/postcss'] ||
528
+ packageJson.devDependencies?.postcss
529
+ ) {
530
+ console.error('Tailwind CSS dependencies must be absent when Tailwind is disabled');
531
+ process.exit(1);
532
+ }
533
+ {{/unless}}
534
+
423
535
  {{#unless isSubproject}}
424
536
  const privateSource = skillsLock.sources?.find(
425
537
  (source) => source.repository === 'https://github.com/TechsioCZ/skills',
@@ -20,6 +20,22 @@ describe('generated UltraModern contract', () => {
20
20
  expect(page).toContain('rel="alternate"');
21
21
  expect(page).toContain('hrefLang="x-default"');
22
22
  expect(page).toContain('localizedPath(');
23
+ {{#if enableTailwind}}
24
+ expect(readText('src/routes/index.css')).toContain(
25
+ "@import 'tailwindcss';",
26
+ );
27
+ expect(readText('postcss.config.mjs')).toContain('@tailwindcss/postcss');
28
+ expect(readText('tailwind.config.ts')).toContain(
29
+ "content: ['./src/**/*.{js,ts,jsx,tsx}']",
30
+ );
31
+ {{/if}}
32
+ {{#unless enableTailwind}}
33
+ expect(readText('src/routes/index.css')).not.toContain(
34
+ "@import 'tailwindcss';",
35
+ );
36
+ expect(fs.existsSync(path.join(root, 'postcss.config.mjs'))).toBe(false);
37
+ expect(fs.existsSync(path.join(root, 'tailwind.config.ts'))).toBe(false);
38
+ {{/unless}}
23
39
 
24
40
  const config = readText('rstest.config.mts');
25
41
  expect(config).toContain("withModernConfig()");
@@ -63,5 +79,17 @@ describe('generated UltraModern contract', () => {
63
79
  expect(
64
80
  packageJson.devDependencies?.['@modern-js/adapter-rstest'],
65
81
  ).toBeTruthy();
82
+ {{#if enableTailwind}}
83
+ expect(packageJson.devDependencies?.tailwindcss).toBe('^4.3.0');
84
+ expect(packageJson.devDependencies?.['@tailwindcss/postcss']).toBe(
85
+ '^4.3.0',
86
+ );
87
+ {{/if}}
88
+ {{#unless enableTailwind}}
89
+ expect(packageJson.devDependencies?.tailwindcss).toBeUndefined();
90
+ expect(
91
+ packageJson.devDependencies?.['@tailwindcss/postcss'],
92
+ ).toBeUndefined();
93
+ {{/unless}}
66
94
  });
67
95
  });
@@ -17,6 +17,20 @@
17
17
  "licensePath": ".agents/rstackjs-agent-skills-LICENSE",
18
18
  "install": "vendored"
19
19
  },
20
+ {
21
+ "id": "module-federation-agent-skills",
22
+ "visibility": "public",
23
+ "repository": "https://github.com/module-federation/agent-skills",
24
+ "commit": "07bb5b6c43ad457609e00c081b72d4c42508ec76",
25
+ "install": "clone",
26
+ "baseline": [
27
+ {
28
+ "name": "mf",
29
+ "path": ".agents/skills/mf",
30
+ "reason": "Module Federation docs, config inspection, type checking, shared dependency checks, and observability troubleshooting"
31
+ }
32
+ ]
33
+ },
20
34
  {
21
35
  "id": "techsiocz-private",
22
36
  "visibility": "private",
@@ -81,6 +95,11 @@
81
95
  "name": "rstest-best-practices",
82
96
  "path": ".agents/skills/rstest-best-practices",
83
97
  "reason": "Rstest configuration, test writing, mocking, snapshots, coverage, and CI behavior"
98
+ },
99
+ {
100
+ "name": "mf",
101
+ "path": ".agents/skills/mf",
102
+ "reason": "Module Federation docs, config inspection, type checking, shared dependency checks, and observability troubleshooting"
84
103
  }
85
104
  ],
86
105
  "excludedByDefault": [
@@ -49,4 +49,4 @@ jobs:
49
49
  - name: Build Workspace Apps
50
50
  env:
51
51
  MODERN_PUBLIC_SITE_URL: http://localhost:8080
52
- run: pnpm -r --filter "./apps/**" run build
52
+ run: pnpm build
@@ -0,0 +1,2 @@
1
+ [tools]
2
+ pnpm = "11.3.0"
@@ -7,14 +7,11 @@ This workspace is generated as an agent-ready UltraModern.js SuperApp. Agents sh
7
7
  - `pnpm lint` runs Oxlint with the Ultracite preset.
8
8
  - `pnpm format` runs oxfmt.
9
9
  - `pnpm typecheck` runs effect-tsgo as the TypeScript checker.
10
- - `pnpm i18n:check` rejects hardcoded user-visible JSX text in generated apps.
11
- - `pnpm check` runs formatting, linting, effect-tsgo, i18n checks, private-skill availability checks, and the generated workspace contract.
10
+ - `pnpm check` runs formatting, linting, effect-tsgo, private-skill availability checks, and the generated workspace contract.
12
11
 
13
- ## Internationalization
12
+ ## Localized Routes
14
13
 
15
- Runtime i18n is enabled by default for generated apps. Agents must put user-visible UI copy in each app's `config/public/locales/<lang>/translation.json` and render it through `react-i18next` or `@modern-js/plugin-i18n/runtime`. Do not add hardcoded JSX text, `aria-label`, `title`, `alt`, or `placeholder` strings unless the value is a non-translatable technical token.
16
-
17
- Routes are locale-prefixed by default through `localePathRedirect: true`. Keep localized app pages under `src/routes/[lang]`, use links for language switching, and preserve canonical plus `hreflang` metadata. Production builds fail unless `MODERN_PUBLIC_SITE_URL` is set per deployed app, so canonical URLs always use the production origin.
14
+ Generated apps keep locale-prefixed entry routes under `src/routes/[lang]`, static language links, and canonical plus `hreflang` metadata. Runtime i18n is not enabled in the starter because the current React 19 + Module Federation streaming SSR stack must render predictably first. Production builds fail unless `MODERN_PUBLIC_SITE_URL` is set per deployed app, so canonical URLs always use the production origin.
18
15
 
19
16
  ## Required Skill Baseline
20
17
 
@@ -27,6 +24,9 @@ Use these skills when the task touches the matching subsystem:
27
24
  - `rslib-best-practices`: Shared packages, generated libraries, declaration output, and Rslib configuration.
28
25
  - `rslib-modern-package`: Package contracts for shared libraries, exports, side effects, dependency placement, README, and release readiness.
29
26
  - `rstest-best-practices`: Rstest configuration, test writing, mocking, snapshots, coverage, and CI test behavior.
27
+ - `mf`: Module Federation docs, Modern.js integration, DTS/type checks, shared dependency checks, runtime errors, and observability troubleshooting.
28
+
29
+ The public `module-federation/agent-skills` repository is installed during `pnpm install` and `pnpm skills:install`. `pnpm skills:check` fails when the required public `mf` skill is missing.
30
30
 
31
31
  ## Private Skills
32
32
 
@@ -58,4 +58,4 @@ Agents may read files under `repos/` to understand upstream patterns, APIs, and
58
58
 
59
59
  ## Skill Provenance
60
60
 
61
- The vendored Rstack skills and private TechsioCZ skill allowlist are pinned in `.agents/skills-lock.json`. Do not update, remove, or replace them casually. If a skill needs updating, update the lock file and run `pnpm check`.
61
+ The vendored Rstack skills, public Module Federation skill, and private TechsioCZ skill allowlist are pinned in `.agents/skills-lock.json`. Do not update, remove, or replace them casually. If a skill needs updating, update the lock file and run `pnpm check`.
@@ -7,12 +7,21 @@ UltraModern.js 3.0 SuperApp surface and scaffolds the canonical Micro Vertical
7
7
  starter topology:
8
8
 
9
9
  - `apps/shell-super-app` owns shell route assembly and remote manifest wiring.
10
- - `apps/remotes/remote-commerce` owns the commerce vertical remote.
11
- - `apps/remotes/remote-identity` owns the identity vertical remote.
10
+ - `apps/remotes/remote-commerce` owns the commerce vertical remote, its
11
+ browser-safe Module Federation exposes, and its Effect BFF/API surface.
12
+ - `apps/remotes/remote-identity` owns the identity vertical remote, its
13
+ browser-safe Module Federation exposes, and its Effect BFF/API surface.
12
14
  - `apps/remotes/remote-design-system` owns the horizontal design-system remote.
13
- - `services/service-recommendations-effect` owns the Effect BFF service.
14
15
  - `packages/shared-*` provide placeholders for cross-workspace contracts.
15
16
 
17
+ Default vertical APIs are owned by the vertical package, not by `services/*`.
18
+ Each full-stack vertical keeps its `api/effect/index.ts` handlers next to its
19
+ `shared/effect/api.ts` contract and `src/effect/*-client.ts` typed client
20
+ surface. The Module Federation config exposes only browser-safe Route and
21
+ Widget modules; server handlers and Effect client/contract modules stay out of
22
+ browser remotes. `services/*` is reserved for explicit external service
23
+ packages.
24
+
16
25
  Run the scaffold validator before adding business code:
17
26
 
18
27
  ```bash
@@ -14,6 +14,10 @@ minimumReleaseAgeExclude:
14
14
  - '@effect/tsgo-*'
15
15
  - '@typescript/native-preview'
16
16
  - '@typescript/native-preview-*'
17
+ - '@cloudflare/*'
18
+ - miniflare
19
+ - workerd
20
+ - wrangler
17
21
  trustPolicy: no-downgrade
18
22
  trustPolicyIgnoreAfter: 1440
19
23
  blockExoticSubdeps: true
@@ -22,9 +26,20 @@ pmOnFail: error
22
26
  verifyDepsBeforeRun: error
23
27
  strictDepBuilds: true
24
28
 
29
+ peerDependencyRules:
30
+ allowedVersions:
31
+ react: '>=19.0.0'
32
+ typescript: '>=6.0.0'
33
+
34
+ overrides:
35
+ '@tanstack/react-router': 1.170.8
36
+ node-fetch: '^3.3.2'
37
+
25
38
  allowBuilds:
26
39
  '@swc/core': true
27
40
  core-js: true
28
41
  esbuild: true
29
42
  msgpackr-extract: true
43
+ sharp: true
30
44
  simple-git-hooks: true
45
+ workerd: true
@@ -0,0 +1,44 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+
4
+ const root = process.cwd();
5
+ const defaultAppDirs = [
6
+ 'apps/remotes/remote-commerce',
7
+ 'apps/remotes/remote-identity',
8
+ 'apps/remotes/remote-design-system',
9
+ ];
10
+
11
+ const candidateDirs = process.argv.slice(2);
12
+ const appDirs = candidateDirs.length
13
+ ? candidateDirs
14
+ : fs.existsSync(path.join(root, 'module-federation.config.ts'))
15
+ ? ['.']
16
+ : defaultAppDirs;
17
+
18
+ for (const appDir of appDirs) {
19
+ const configPath = path.join(root, appDir, 'module-federation.config.ts');
20
+ if (!fs.existsSync(configPath)) {
21
+ throw new Error(
22
+ `Missing Module Federation config: ${path.relative(root, configPath)}`,
23
+ );
24
+ }
25
+
26
+ const config = fs.readFileSync(configPath, 'utf-8');
27
+ if (!config.includes('exposes:') || config.includes('dts: false')) {
28
+ continue;
29
+ }
30
+
31
+ const typesArchivePath = path.join(root, appDir, 'dist/@mf-types.zip');
32
+ if (!fs.existsSync(typesArchivePath)) {
33
+ throw new Error(
34
+ `Missing Module Federation DTS archive: ${path.relative(root, typesArchivePath)}`,
35
+ );
36
+ }
37
+
38
+ const stats = fs.statSync(typesArchivePath);
39
+ if (stats.size === 0) {
40
+ throw new Error(
41
+ `Empty Module Federation DTS archive: ${path.relative(root, typesArchivePath)}`,
42
+ );
43
+ }
44
+ }