@bleedingdev/modern-js-create 3.2.0-ultramodern.20 → 3.2.0-ultramodern.22

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.
@@ -1,10 +1,15 @@
1
+ import { execFileSync } from 'node:child_process';
1
2
  import fs from 'node:fs';
2
3
  import path from 'node:path';
3
4
 
4
5
  const root = process.cwd();
5
6
  const packageScope = '{{packageScope}}';
6
- const tanstackVersion = '1.170.1';
7
+ const tanstackVersion = '1.170.8';
8
+ const tailwindEnabled = '{{tailwindEnabled}}' === 'true';
9
+ const tailwindVersion = '^4.3.0';
10
+ const tailwindPostcssVersion = '^4.3.0';
7
11
  const rstackAgentSkillsCommit = '61c948b42512e223bad44b83af4080eba48b2677';
12
+ const moduleFederationAgentSkillsCommit = '07bb5b6c43ad457609e00c081b72d4c42508ec76';
8
13
  const modernPackages = [
9
14
  '@modern-js/app-tools',
10
15
  '@modern-js/plugin-bff',
@@ -21,10 +26,47 @@ const baselineAgentSkills = [
21
26
  'rslib-modern-package',
22
27
  'rstest-best-practices',
23
28
  ];
29
+ const moduleFederationAgentSkills = ['mf'];
24
30
  const privateAgentSkills = ['plan-graph', 'dag', 'subagent-graph', 'helm', 'debugger-mode'];
31
+ const fullStackVerticals = [
32
+ {
33
+ id: 'remote-commerce',
34
+ domain: 'commerce',
35
+ stem: 'recommendations',
36
+ group: 'recommendations',
37
+ path: 'apps/remotes/remote-commerce',
38
+ mfName: 'remoteCommerce',
39
+ apiPrefix: '/commerce-api',
40
+ },
41
+ {
42
+ id: 'remote-identity',
43
+ domain: 'identity',
44
+ stem: 'identity',
45
+ group: 'identity',
46
+ path: 'apps/remotes/remote-identity',
47
+ mfName: 'remoteIdentity',
48
+ apiPrefix: '/identity-api',
49
+ },
50
+ ];
51
+ const designSystemRemotePath = 'apps/remotes/remote-design-system';
25
52
 
26
53
  const readText = (relativePath) => fs.readFileSync(path.join(root, relativePath), 'utf-8');
27
54
  const readJson = (relativePath) => JSON.parse(readText(relativePath));
55
+ const readPnpmConfig = (key) => {
56
+ const env = { ...process.env };
57
+ for (const envKey of Object.keys(env)) {
58
+ if (/^(?:npm|pnpm)_config_/i.test(envKey)) {
59
+ delete env[envKey];
60
+ }
61
+ }
62
+ const output = execFileSync('pnpm', ['config', 'get', key, '--json'], {
63
+ cwd: root,
64
+ env,
65
+ encoding: 'utf-8',
66
+ stdio: ['ignore', 'pipe', 'pipe'],
67
+ }).trim();
68
+ return output ? JSON.parse(output) : undefined;
69
+ };
28
70
  const assert = (condition, message) => {
29
71
  if (!condition) {
30
72
  throw new Error(message);
@@ -33,6 +75,9 @@ const assert = (condition, message) => {
33
75
  const assertExists = (relativePath) => {
34
76
  assert(fs.existsSync(path.join(root, relativePath)), `Missing ${relativePath}`);
35
77
  };
78
+ const assertNotExists = (relativePath) => {
79
+ assert(!fs.existsSync(path.join(root, relativePath)), `Unexpected ${relativePath}`);
80
+ };
36
81
 
37
82
  const requiredPaths = [
38
83
  'AGENTS.md',
@@ -62,54 +107,76 @@ const requiredPaths = [
62
107
  'topology/local-overlays/development.json',
63
108
  '.modernjs/ultramodern-workspace-template-manifest.json',
64
109
  '.modernjs/ultramodern-package-source.json',
110
+ '.modernjs/ultramodern-generated-contract.json',
111
+ 'scripts/assert-mf-types.mjs',
65
112
  'scripts/bootstrap-agent-skills.mjs',
66
113
  'scripts/setup-agent-reference-repos.mjs',
67
- 'scripts/check-i18n-strings.mjs',
68
114
  'apps/shell-super-app/package.json',
69
- 'apps/shell-super-app/config/public/locales/en/translation.json',
70
- 'apps/shell-super-app/config/public/locales/cs/translation.json',
71
115
  'apps/shell-super-app/modern.config.ts',
72
116
  'apps/shell-super-app/module-federation.config.ts',
73
117
  'apps/shell-super-app/src/modern-app-env.d.ts',
74
118
  'apps/shell-super-app/src/modern.runtime.ts',
119
+ 'apps/shell-super-app/locales/en/translation.json',
120
+ 'apps/shell-super-app/locales/cs/translation.json',
121
+ 'apps/shell-super-app/src/routes/index.css',
75
122
  'apps/shell-super-app/src/routes/[lang]/page.tsx',
76
123
  'apps/remotes/remote-commerce/package.json',
77
- 'apps/remotes/remote-commerce/config/public/locales/en/translation.json',
78
- 'apps/remotes/remote-commerce/config/public/locales/cs/translation.json',
79
124
  'apps/remotes/remote-commerce/modern.config.ts',
80
125
  'apps/remotes/remote-commerce/module-federation.config.ts',
126
+ 'apps/remotes/remote-commerce/api/effect/index.ts',
127
+ 'apps/remotes/remote-commerce/shared/effect/api.ts',
128
+ 'apps/remotes/remote-commerce/src/effect/recommendations-client.ts',
81
129
  'apps/remotes/remote-commerce/src/modern-app-env.d.ts',
82
130
  'apps/remotes/remote-commerce/src/modern.runtime.ts',
131
+ 'apps/remotes/remote-commerce/locales/en/translation.json',
132
+ 'apps/remotes/remote-commerce/locales/cs/translation.json',
133
+ 'apps/remotes/remote-commerce/src/routes/index.css',
83
134
  'apps/remotes/remote-commerce/src/routes/[lang]/page.tsx',
84
135
  'apps/remotes/remote-identity/package.json',
85
- 'apps/remotes/remote-identity/config/public/locales/en/translation.json',
86
- 'apps/remotes/remote-identity/config/public/locales/cs/translation.json',
87
136
  'apps/remotes/remote-identity/modern.config.ts',
88
137
  'apps/remotes/remote-identity/module-federation.config.ts',
138
+ 'apps/remotes/remote-identity/api/effect/index.ts',
139
+ 'apps/remotes/remote-identity/shared/effect/api.ts',
140
+ 'apps/remotes/remote-identity/src/effect/identity-client.ts',
89
141
  'apps/remotes/remote-identity/src/modern-app-env.d.ts',
90
142
  'apps/remotes/remote-identity/src/modern.runtime.ts',
143
+ 'apps/remotes/remote-identity/locales/en/translation.json',
144
+ 'apps/remotes/remote-identity/locales/cs/translation.json',
145
+ 'apps/remotes/remote-identity/src/routes/index.css',
91
146
  'apps/remotes/remote-identity/src/routes/[lang]/page.tsx',
92
147
  'apps/remotes/remote-design-system/package.json',
93
- 'apps/remotes/remote-design-system/config/public/locales/en/translation.json',
94
- 'apps/remotes/remote-design-system/config/public/locales/cs/translation.json',
95
148
  'apps/remotes/remote-design-system/modern.config.ts',
96
149
  'apps/remotes/remote-design-system/module-federation.config.ts',
97
150
  'apps/remotes/remote-design-system/src/modern-app-env.d.ts',
98
151
  'apps/remotes/remote-design-system/src/modern.runtime.ts',
152
+ 'apps/remotes/remote-design-system/locales/en/translation.json',
153
+ 'apps/remotes/remote-design-system/locales/cs/translation.json',
154
+ 'apps/remotes/remote-design-system/src/routes/index.css',
99
155
  'apps/remotes/remote-design-system/src/routes/[lang]/page.tsx',
100
- 'services/service-recommendations-effect/package.json',
101
- 'services/service-recommendations-effect/modern.config.ts',
102
- 'services/service-recommendations-effect/api/effect/index.ts',
103
- 'services/service-recommendations-effect/shared/effect/api.ts',
104
156
  'packages/shared-contracts/src/index.ts',
105
157
  'packages/shared-design-tokens/src/index.ts',
106
158
  'packages/shared-effect-api/src/index.ts',
107
159
  ];
108
160
 
161
+ if (tailwindEnabled) {
162
+ requiredPaths.push(
163
+ 'apps/shell-super-app/postcss.config.mjs',
164
+ 'apps/shell-super-app/tailwind.config.ts',
165
+ 'apps/remotes/remote-commerce/postcss.config.mjs',
166
+ 'apps/remotes/remote-commerce/tailwind.config.ts',
167
+ 'apps/remotes/remote-identity/postcss.config.mjs',
168
+ 'apps/remotes/remote-identity/tailwind.config.ts',
169
+ 'apps/remotes/remote-design-system/postcss.config.mjs',
170
+ 'apps/remotes/remote-design-system/tailwind.config.ts',
171
+ );
172
+ }
173
+
109
174
  for (const requiredPath of requiredPaths) {
110
175
  assertExists(requiredPath);
111
176
  }
112
177
 
178
+ assertNotExists('services/service-recommendations-effect');
179
+
113
180
  for (const appDirectory of [
114
181
  'apps/shell-super-app',
115
182
  'apps/remotes/remote-commerce',
@@ -126,10 +193,8 @@ const rootPackage = readJson('package.json');
126
193
  const packageSource = readJson('.modernjs/ultramodern-package-source.json');
127
194
  const skillsLock = readJson('.agents/skills-lock.json');
128
195
  const agentReferenceRepos = readJson('.agents/agent-reference-repos.json');
129
- const pnpmWorkspace = readText('pnpm-workspace.yaml');
130
196
  const workflowContent = readText('.github/workflows/ultramodern-workspace-gates.yml');
131
197
  const renovateConfig = readJson('.github/renovate.json');
132
- const deprecatedTanstackRuntime = '@modern-js/runtime/' + 'tanstack-router';
133
198
  const expectedModernSpecifier =
134
199
  packageSource.strategy === 'install' ? packageSource.modernPackages?.specifier : 'workspace:*';
135
200
 
@@ -146,38 +211,75 @@ const expectedModernDependency = (packageName) => {
146
211
 
147
212
  assert(rootPackage.private === true, 'Root package must be private');
148
213
  assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare presetUltramodern');
149
- assert(rootPackage.packageManager === 'pnpm@11.1.2', 'Root must pin pnpm 11.1.2');
150
- assert(rootPackage.engines?.pnpm === '>=11.0.0', 'Root must require pnpm >=11');
151
- for (const requiredSnippet of [
152
- 'packages:\n - apps/*\n - apps/remotes/*\n - services/*\n - packages/*',
153
- 'minimumReleaseAge: 1440',
154
- 'minimumReleaseAgeStrict: true',
155
- 'minimumReleaseAgeIgnoreMissingTime: false',
156
- "minimumReleaseAgeExclude:\n - '@modern-js/*'\n - '@bleedingdev/*'\n - '@effect/tsgo'\n - '@effect/tsgo-*'\n - '@typescript/native-preview'\n - '@typescript/native-preview-*'",
157
- 'trustPolicy: no-downgrade',
158
- 'trustPolicyIgnoreAfter: 1440',
159
- 'blockExoticSubdeps: true',
160
- 'engineStrict: true',
161
- 'pmOnFail: error',
162
- 'verifyDepsBeforeRun: error',
163
- 'strictDepBuilds: true',
164
- "allowBuilds:\n '@swc/core': true\n core-js: true\n esbuild: true\n msgpackr-extract: true\n simple-git-hooks: true",
165
- ]) {
166
- assert(
167
- pnpmWorkspace.includes(requiredSnippet),
168
- `pnpm-workspace.yaml must retain ${requiredSnippet.split('\n')[0]}`,
169
- );
170
- }
171
- assert(
172
- !pnpmWorkspace.includes('onlyBuiltDependencies'),
173
- 'pnpm-workspace.yaml must use pnpm 11 allowBuilds instead of onlyBuiltDependencies',
174
- );
214
+ assert(rootPackage.packageManager === 'pnpm@11.3.0', 'Root must pin pnpm 11.3.0');
215
+ assert(
216
+ rootPackage.engines?.pnpm === '>=11.3.0 <11.4.0',
217
+ 'Root must require pnpm >=11.3.0 <11.4.0',
218
+ );
219
+ assert(
220
+ JSON.stringify(readPnpmConfig('packages')) ===
221
+ JSON.stringify(['apps/*', 'apps/remotes/*', 'services/*', 'packages/*']),
222
+ 'pnpm-workspace.yaml must retain workspace package globs',
223
+ );
224
+ assert(readPnpmConfig('minimumReleaseAge') === 1440, 'pnpm minimumReleaseAge must be 1440');
225
+ assert(readPnpmConfig('minimumReleaseAgeStrict') === true, 'pnpm minimumReleaseAgeStrict must be true');
226
+ assert(
227
+ readPnpmConfig('minimumReleaseAgeIgnoreMissingTime') === false,
228
+ 'pnpm minimumReleaseAgeIgnoreMissingTime must be false',
229
+ );
230
+ assert(
231
+ JSON.stringify(readPnpmConfig('minimumReleaseAgeExclude')) ===
232
+ JSON.stringify([
233
+ '@modern-js/*',
234
+ '@bleedingdev/*',
235
+ '@effect/tsgo',
236
+ '@effect/tsgo-*',
237
+ '@typescript/native-preview',
238
+ '@typescript/native-preview-*',
239
+ '@cloudflare/*',
240
+ 'miniflare',
241
+ 'workerd',
242
+ 'wrangler',
243
+ ]),
244
+ 'pnpm minimumReleaseAgeExclude must retain framework and Cloudflare exceptions',
245
+ );
246
+ assert(readPnpmConfig('trustPolicy') === 'no-downgrade', 'pnpm trustPolicy must be no-downgrade');
247
+ assert(readPnpmConfig('trustPolicyIgnoreAfter') === 1440, 'pnpm trustPolicyIgnoreAfter must be 1440');
248
+ assert(readPnpmConfig('blockExoticSubdeps') === true, 'pnpm blockExoticSubdeps must be true');
249
+ assert(readPnpmConfig('engineStrict') === true, 'pnpm engineStrict must be true');
250
+ assert(readPnpmConfig('pmOnFail') === 'error', 'pnpm pmOnFail must be error');
251
+ assert(readPnpmConfig('verifyDepsBeforeRun') === 'error', 'pnpm verifyDepsBeforeRun must be error');
252
+ assert(readPnpmConfig('strictDepBuilds') === true, 'pnpm strictDepBuilds must be true');
253
+ assert(
254
+ JSON.stringify(readPnpmConfig('peerDependencyRules')) ===
255
+ JSON.stringify({ allowedVersions: { react: '>=19.0.0', typescript: '>=6.0.0' } }),
256
+ 'pnpm peerDependencyRules must allow React 19 and TypeScript 6+',
257
+ );
258
+ assert(
259
+ JSON.stringify(readPnpmConfig('overrides')) ===
260
+ JSON.stringify({ '@tanstack/react-router': '1.170.8', 'node-fetch': '^3.3.2' }),
261
+ 'pnpm overrides must pin generated router and node-fetch policy',
262
+ );
263
+ assert(
264
+ JSON.stringify(readPnpmConfig('allowBuilds')) ===
265
+ JSON.stringify({
266
+ '@swc/core': true,
267
+ 'core-js': true,
268
+ esbuild: true,
269
+ 'msgpackr-extract': true,
270
+ sharp: true,
271
+ 'simple-git-hooks': true,
272
+ workerd: true,
273
+ }),
274
+ 'pnpm allowBuilds must approve only the generated workspace build dependencies',
275
+ );
276
+ assert(readPnpmConfig('onlyBuiltDependencies') === undefined, 'pnpm onlyBuiltDependencies must not be set');
175
277
  for (const requiredSnippet of [
176
278
  'permissions:\n contents: read',
177
279
  'pull_request:',
178
280
  'persist-credentials: false',
179
281
  'pnpm install --frozen-lockfile',
180
- 'pnpm -r --filter "./apps/**" run build',
282
+ 'pnpm build',
181
283
  'MODERN_PUBLIC_SITE_URL: http://localhost:8080',
182
284
  'timeout-minutes:',
183
285
  'egress-policy: audit',
@@ -236,22 +338,29 @@ assert(
236
338
  );
237
339
 
238
340
  const requiredRootScripts = {
341
+ build:
342
+ 'pnpm -r --filter "./apps/remotes/**" run build && pnpm --filter "./apps/shell-super-app" run build && pnpm ultramodern:assert-mf-types',
239
343
  format: 'oxfmt .',
240
344
  'format:check': 'oxfmt --check .',
241
- 'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
242
345
  lint: 'oxlint .',
243
346
  'lint:fix': 'oxlint . --fix',
244
347
  'agents:refs:check': 'node ./scripts/setup-agent-reference-repos.mjs --check',
245
348
  'agents:refs:install': 'node ./scripts/setup-agent-reference-repos.mjs',
246
- postinstall: 'node ./scripts/setup-agent-reference-repos.mjs',
349
+ postinstall:
350
+ 'node ./scripts/setup-agent-reference-repos.mjs && node ./scripts/bootstrap-agent-skills.mjs',
247
351
  'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
248
352
  'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
249
353
  typecheck: `pnpm -r --filter "@${packageScope}/*" typecheck`,
354
+ 'ultramodern:assert-mf-types': 'node ./scripts/assert-mf-types.mjs',
250
355
  'ultramodern:check': 'node ./scripts/validate-ultramodern-workspace.mjs',
251
356
  };
252
357
  for (const [scriptName, scriptCommand] of Object.entries(requiredRootScripts)) {
253
358
  assert(rootPackage.scripts?.[scriptName] === scriptCommand, `Root must expose ${scriptName}`);
254
359
  }
360
+ assert(
361
+ Object.keys(rootPackage.scripts ?? {}).every((scriptName) => !scriptName.startsWith('zephyr:')),
362
+ 'Root package must not expose custom zephyr:* lifecycle commands',
363
+ );
255
364
 
256
365
  for (const dependency of [
257
366
  '@effect/tsgo',
@@ -285,6 +394,10 @@ assert(
285
394
  agentReferenceRepos.defaultEnabled === true,
286
395
  'Agent reference repositories must be enabled by default',
287
396
  );
397
+ assert(
398
+ agentReferenceRepos.strategy === 'git-subtree-squash',
399
+ 'Agent reference repositories must use git subtree squash strategy',
400
+ );
288
401
  assert(
289
402
  agentReferenceRepos.repositories?.some(
290
403
  (repo) =>
@@ -309,6 +422,10 @@ assert(
309
422
  ),
310
423
  'Agent reference repository setup must expose an opt-out environment variable',
311
424
  );
425
+ assert(
426
+ readText('scripts/setup-agent-reference-repos.mjs').includes('git-subtree-dir'),
427
+ 'Agent reference repository setup must validate git subtree evidence',
428
+ );
312
429
  for (const skillName of baselineAgentSkills) {
313
430
  assert(
314
431
  skillsLock.baseline?.some((skill) => skill.name === skillName),
@@ -323,6 +440,26 @@ for (const skillName of baselineAgentSkills) {
323
440
  const privateSource = skillsLock.sources?.find(
324
441
  (source) => source.repository === 'https://github.com/TechsioCZ/skills',
325
442
  );
443
+ const moduleFederationSource = skillsLock.sources?.find(
444
+ (source) => source.repository === 'https://github.com/module-federation/agent-skills',
445
+ );
446
+ assert(
447
+ moduleFederationSource?.install === 'clone' &&
448
+ moduleFederationSource.commit === moduleFederationAgentSkillsCommit,
449
+ 'Agent skills lock must configure the public Module Federation skill source as a pinned clone',
450
+ );
451
+ for (const skillName of moduleFederationAgentSkills) {
452
+ assert(
453
+ skillsLock.baseline?.some((skill) => skill.name === skillName) &&
454
+ moduleFederationSource.baseline?.some((skill) => skill.name === skillName),
455
+ `Agent skills lock must require Module Federation skill ${skillName}`,
456
+ );
457
+ assert(
458
+ agentsInstructions.includes(`\`${skillName}\``) &&
459
+ agentsInstructions.includes('module-federation/agent-skills'),
460
+ `Root AGENTS.md must document required Module Federation skill ${skillName}`,
461
+ );
462
+ }
326
463
  assert(
327
464
  privateSource?.install === 'clone-if-authorized',
328
465
  'Agent skills lock must configure TechsioCZ skills as clone-if-authorized',
@@ -338,18 +475,52 @@ const appPackagePaths = [
338
475
  'apps/shell-super-app/package.json',
339
476
  'apps/remotes/remote-commerce/package.json',
340
477
  'apps/remotes/remote-identity/package.json',
341
- 'apps/remotes/remote-design-system/package.json',
478
+ `${designSystemRemotePath}/package.json`,
342
479
  ];
343
480
 
481
+ const expectedZephyrDependencies = {
482
+ commerce: `@${packageScope}/remote-commerce@workspace:*`,
483
+ identity: `@${packageScope}/remote-identity@workspace:*`,
484
+ designSystem: `@${packageScope}/remote-design-system@workspace:*`,
485
+ };
486
+
344
487
  for (const packagePath of appPackagePaths) {
345
488
  const packageJson = readJson(packagePath);
489
+ const isRemote = packagePath.includes('/remotes/');
490
+ const fullStackVertical = fullStackVerticals.find(
491
+ (vertical) => `${vertical.path}/package.json` === packagePath,
492
+ );
493
+ assert(packageJson.scripts?.dev === 'modern dev', `${packagePath} must use vanilla modern dev`);
346
494
  assert(
347
- packageJson.dependencies?.['@modern-js/plugin-i18n'] ===
348
- expectedModernDependency('@modern-js/plugin-i18n'),
349
- `${packagePath} must use @modern-js/plugin-i18n through ${expectedModernDependency(
350
- '@modern-js/plugin-i18n',
351
- )}`,
495
+ packageJson.scripts?.build ===
496
+ (isRemote
497
+ ? 'modern build && node ../../../scripts/assert-mf-types.mjs'
498
+ : 'modern build'),
499
+ `${packagePath} must enforce Module Federation DTS on remote builds`,
500
+ );
501
+ assert(packageJson.scripts?.serve === 'modern serve', `${packagePath} must use vanilla modern serve`);
502
+ assert(
503
+ Object.keys(packageJson.scripts ?? {}).every((scriptName) => !scriptName.startsWith('zephyr:')),
504
+ `${packagePath} must not expose custom zephyr:* lifecycle commands`,
352
505
  );
506
+ assert(
507
+ packageJson['zephyr:dependencies'] &&
508
+ typeof packageJson['zephyr:dependencies'] === 'object' &&
509
+ !Array.isArray(packageJson['zephyr:dependencies']),
510
+ `${packagePath} must declare zephyr:dependencies for deterministic Zephyr dependency extraction`,
511
+ );
512
+ if (isRemote) {
513
+ assert(
514
+ Object.keys(packageJson['zephyr:dependencies']).length === 0,
515
+ `${packagePath} must declare an empty zephyr:dependencies map until it consumes remotes`,
516
+ );
517
+ } else {
518
+ assert(
519
+ JSON.stringify(packageJson['zephyr:dependencies']) ===
520
+ JSON.stringify(expectedZephyrDependencies),
521
+ `${packagePath} must map shell remote aliases to Zephyr workspace remote package dependencies`,
522
+ );
523
+ }
353
524
  assert(
354
525
  packageJson.dependencies?.['@modern-js/plugin-tanstack'] ===
355
526
  expectedModernDependency('@modern-js/plugin-tanstack'),
@@ -357,6 +528,13 @@ for (const packagePath of appPackagePaths) {
357
528
  '@modern-js/plugin-tanstack',
358
529
  )}`,
359
530
  );
531
+ assert(
532
+ packageJson.dependencies?.['@modern-js/plugin-i18n'] ===
533
+ expectedModernDependency('@modern-js/plugin-i18n'),
534
+ `${packagePath} must use @modern-js/plugin-i18n through ${expectedModernDependency(
535
+ '@modern-js/plugin-i18n',
536
+ )}`,
537
+ );
360
538
  assert(
361
539
  packageJson.dependencies?.['@modern-js/runtime'] ===
362
540
  expectedModernDependency('@modern-js/runtime'),
@@ -371,6 +549,22 @@ for (const packagePath of appPackagePaths) {
371
549
  '@modern-js/app-tools',
372
550
  )}`,
373
551
  );
552
+ assert(
553
+ packageJson.devDependencies?.typescript === '6.0.3',
554
+ `${packagePath} must include TypeScript 6 only as the stable JS TypeScript package for Module Federation DTS generation`,
555
+ );
556
+ assert(
557
+ packageJson.devDependencies?.['@typescript/native-preview'] === '7.0.0-dev.20260526.1',
558
+ `${packagePath} must include TypeScript 7 native preview for mandatory native typechecking`,
559
+ );
560
+ assert(
561
+ packageJson.devDependencies?.['zephyr-rspack-plugin'] === '1.1.1',
562
+ `${packagePath} must install the Zephyr Rspack plugin used by the Modern.js bridge`,
563
+ );
564
+ assert(
565
+ packageJson.devDependencies?.['zephyr-modernjs-plugin'] === undefined,
566
+ `${packagePath} must not install the inactive Zephyr Modern.js wrapper`,
567
+ );
374
568
  assert(
375
569
  packageJson.dependencies?.[`@${packageScope}/shared-contracts`] === 'workspace:*',
376
570
  `${packagePath} must link generated shared contracts through workspace:*`,
@@ -379,29 +573,131 @@ for (const packagePath of appPackagePaths) {
379
573
  packageJson.dependencies?.[`@${packageScope}/shared-design-tokens`] === 'workspace:*',
380
574
  `${packagePath} must link generated shared design tokens through workspace:*`,
381
575
  );
382
- assert(packageJson.dependencies?.i18next === '26.2.0', `${packagePath} must include i18next`);
383
576
  assert(
384
- packageJson.dependencies?.['react-i18next'] === '17.0.8',
385
- `${packagePath} must include react-i18next`,
577
+ packageJson.dependencies?.['node-fetch'] === '^3.3.2',
578
+ `${packagePath} must satisfy the Module Federation SDK node-fetch peer`,
386
579
  );
387
580
  assert(
388
581
  packageJson.dependencies?.['@tanstack/react-router'] === tanstackVersion,
389
582
  `${packagePath} must use @tanstack/react-router ${tanstackVersion}`,
390
583
  );
391
584
  assert(
392
- packageJson.dependencies?.['@module-federation/modern-js-v3'] === '2.4.0',
393
- `${packagePath} must include the Module Federation plugin`,
585
+ packageJson.dependencies?.i18next === '26.2.0',
586
+ `${packagePath} must include i18next for mandatory native Modern.js i18n`,
394
587
  );
395
588
  assert(
396
- packageJson.dependencies?.['zephyr-modernjs-plugin'] === '1.1.1',
397
- `${packagePath} must include the official Zephyr Modern.js plugin`,
589
+ !packageJson.dependencies?.['react-i18next'],
590
+ `${packagePath} must use native @modern-js/plugin-i18n hooks instead of react-i18next in the TanStack MF starter`,
398
591
  );
592
+ assert(
593
+ packageJson.dependencies?.['@module-federation/modern-js-v3'] === '2.5.0',
594
+ `${packagePath} must include the Module Federation plugin`,
595
+ );
596
+ if (fullStackVertical) {
597
+ assert(
598
+ packageJson.dependencies?.['@modern-js/plugin-bff'] ===
599
+ expectedModernDependency('@modern-js/plugin-bff'),
600
+ `${packagePath} must use @modern-js/plugin-bff through ${expectedModernDependency(
601
+ '@modern-js/plugin-bff',
602
+ )}`,
603
+ );
604
+ assert(
605
+ packageJson.exports?.['./effect/client'] ===
606
+ `./src/effect/${fullStackVertical.stem}-client.ts` &&
607
+ packageJson.exports?.['./shared/effect/api'] === './shared/effect/api.ts',
608
+ `${packagePath} must expose its vertical-owned Effect client and contract surface`,
609
+ );
610
+ }
611
+ if (packagePath === `${designSystemRemotePath}/package.json`) {
612
+ assert(
613
+ !packageJson.dependencies?.['@modern-js/plugin-bff'],
614
+ 'Design-system remote must remain FE-only and must not depend on @modern-js/plugin-bff',
615
+ );
616
+ assert(
617
+ !packageJson.exports?.['./effect/client'] && !packageJson.exports?.['./shared/effect/api'],
618
+ 'Design-system remote must not expose Effect client or contract exports',
619
+ );
620
+ }
621
+ if (tailwindEnabled) {
622
+ assert(
623
+ packageJson.devDependencies?.tailwindcss === tailwindVersion,
624
+ `${packagePath} must include Tailwind CSS ${tailwindVersion}`,
625
+ );
626
+ assert(
627
+ packageJson.devDependencies?.['@tailwindcss/postcss'] === tailwindPostcssVersion,
628
+ `${packagePath} must include @tailwindcss/postcss ${tailwindPostcssVersion}`,
629
+ );
630
+ assert(
631
+ packageJson.devDependencies?.postcss === '^8.5.6',
632
+ `${packagePath} must include PostCSS for Tailwind CSS`,
633
+ );
634
+ } else {
635
+ assert(!packageJson.devDependencies?.tailwindcss, `${packagePath} must not include Tailwind CSS`);
636
+ assert(
637
+ !packageJson.devDependencies?.['@tailwindcss/postcss'],
638
+ `${packagePath} must not include @tailwindcss/postcss`,
639
+ );
640
+ }
399
641
  assert(
400
642
  packageJson.modernjs?.preset === 'presetUltramodern',
401
643
  `${packagePath} must keep presetUltramodern metadata`,
402
644
  );
403
645
  }
404
646
 
647
+ for (const federationConfigPath of [
648
+ 'apps/shell-super-app/module-federation.config.ts',
649
+ 'apps/remotes/remote-commerce/module-federation.config.ts',
650
+ 'apps/remotes/remote-identity/module-federation.config.ts',
651
+ 'apps/remotes/remote-design-system/module-federation.config.ts',
652
+ ]) {
653
+ const federationConfig = readText(federationConfigPath);
654
+ assert(
655
+ federationConfig.includes('compilerInstance:') &&
656
+ federationConfig.includes("'--package typescript -- tsc'") &&
657
+ federationConfig.includes('displayErrorInTerminal: true') &&
658
+ !federationConfig.includes('dts: false'),
659
+ `${federationConfigPath} must keep strict Module Federation DTS enabled`,
660
+ );
661
+ }
662
+
663
+ for (const appDirectory of [
664
+ 'apps/shell-super-app',
665
+ 'apps/remotes/remote-commerce',
666
+ 'apps/remotes/remote-identity',
667
+ 'apps/remotes/remote-design-system',
668
+ ]) {
669
+ const css = readText(`${appDirectory}/src/routes/index.css`);
670
+ if (tailwindEnabled) {
671
+ assert(
672
+ css.includes("@import 'tailwindcss';"),
673
+ `${appDirectory} must import Tailwind CSS by default`,
674
+ );
675
+ const postcssConfig = readText(`${appDirectory}/postcss.config.mjs`);
676
+ assert(
677
+ postcssConfig.includes("'@tailwindcss/postcss'"),
678
+ `${appDirectory} must configure the Tailwind CSS PostCSS plugin`,
679
+ );
680
+ const tailwindConfig = readText(`${appDirectory}/tailwind.config.ts`);
681
+ assert(
682
+ tailwindConfig.includes("content: ['./src/**/*.{js,jsx,ts,tsx}']"),
683
+ `${appDirectory} must scan app source files for Tailwind CSS classes`,
684
+ );
685
+ } else {
686
+ assert(
687
+ !css.includes("@import 'tailwindcss';"),
688
+ `${appDirectory} must omit Tailwind CSS import when Tailwind is disabled`,
689
+ );
690
+ assert(
691
+ !fs.existsSync(path.join(root, appDirectory, 'postcss.config.mjs')),
692
+ `${appDirectory} must not write PostCSS config when Tailwind is disabled`,
693
+ );
694
+ assert(
695
+ !fs.existsSync(path.join(root, appDirectory, 'tailwind.config.ts')),
696
+ `${appDirectory} must not write Tailwind config when Tailwind is disabled`,
697
+ );
698
+ }
699
+ }
700
+
405
701
  for (const configPath of [
406
702
  'apps/shell-super-app/modern.config.ts',
407
703
  'apps/remotes/remote-commerce/modern.config.ts',
@@ -410,26 +706,29 @@ for (const configPath of [
410
706
  ]) {
411
707
  const config = readText(configPath);
412
708
  assert(config.includes('presetUltramodern('), `${configPath} must use presetUltramodern`);
413
- assert(config.includes('i18nPlugin('), `${configPath} must enable plugin-i18n`);
414
- assert(config.includes('localePathRedirect: true'), `${configPath} must prefix localized URLs`);
415
709
  assert(config.includes('ULTRAMODERN_SITE_URL'), `${configPath} must expose site URL metadata`);
416
710
  assert(
417
711
  config.includes('MODERN_PUBLIC_SITE_URL must be set for production builds'),
418
712
  `${configPath} must require MODERN_PUBLIC_SITE_URL for production builds`,
419
713
  );
420
714
  assert(config.includes('tanstackRouterPlugin()'), `${configPath} must enable plugin-tanstack`);
715
+ assert(config.includes('i18nPlugin('), `${configPath} must enable native Modern.js i18n`);
421
716
  assert(
422
- config.includes('moduleFederationPlugin()'),
423
- `${configPath} must enable Module Federation`,
717
+ config.includes('reactI18next: false'),
718
+ `${configPath} must use the native Modern.js i18n provider without optional react-i18next probing`,
719
+ );
720
+ assert(
721
+ config.includes("publicDir: './locales'"),
722
+ `${configPath} must expose app-owned locale JSON through Modern.js publicDir`,
424
723
  );
425
724
  assert(
426
- config.includes("from 'zephyr-modernjs-plugin'"),
427
- `${configPath} must import the official Zephyr Modern.js plugin`,
725
+ config.includes('moduleFederationPlugin()'),
726
+ `${configPath} must enable Module Federation`,
428
727
  );
429
- assert(config.includes('withZephyr()'), `${configPath} must enable Zephyr through plugins`);
430
728
  assert(
431
- config.includes("bundler: 'rspack'"),
432
- `${configPath} must keep appTools on the rspack bundler for Zephyr`,
729
+ config.includes("mode: 'stream'") &&
730
+ config.includes('moduleFederationAppSSR: true'),
731
+ `${configPath} must use streaming Module Federation SSR`,
433
732
  );
434
733
  assert(
435
734
  config.includes("html: './'"),
@@ -456,18 +755,15 @@ for (const routePath of [
456
755
  assert(route.includes('rel="alternate"'), `${routePath} must emit alternate locale metadata`);
457
756
  assert(route.includes('hrefLang="x-default"'), `${routePath} must emit x-default metadata`);
458
757
  assert(route.includes('localizedPath('), `${routePath} must build localized URLs`);
459
- assert(
460
- route.includes('@modern-js/plugin-tanstack/runtime'),
461
- `${routePath} must import TanStack runtime from @modern-js/plugin-tanstack/runtime`,
462
- );
463
- assert(
464
- !route.includes(deprecatedTanstackRuntime),
465
- `${routePath} must not import deprecated TanStack runtime path`,
466
- );
758
+ assert(route.includes("import '../index.css';"), `${routePath} must import route CSS`);
467
759
  }
468
760
 
469
761
  const shellMf = readText('apps/shell-super-app/module-federation.config.ts');
470
762
  assert(shellMf.includes("name: 'shellSuperApp'"), 'Shell MF config must name the shell');
763
+ assert(
764
+ shellMf.includes("'react-dom/client'"),
765
+ 'Shell MF config must share react-dom/client explicitly',
766
+ );
471
767
  assert(
472
768
  shellMf.includes('remoteCommerce@http://localhost:3021/mf-manifest.json'),
473
769
  'Shell must reference commerce remote',
@@ -481,61 +777,74 @@ assert(
481
777
  'Shell must reference design-system remote',
482
778
  );
483
779
 
484
- const commerceMf = readText('apps/remotes/remote-commerce/module-federation.config.ts');
485
- assert(commerceMf.includes("name: 'remoteCommerce'"), 'Commerce remote MF name is missing');
486
- assert(commerceMf.includes("'./Widget'"), 'Commerce remote must expose a widget');
487
-
488
- const designMf = readText('apps/remotes/remote-design-system/module-federation.config.ts');
489
- assert(designMf.includes("name: 'remoteDesignSystem'"), 'Design-system remote MF name is missing');
490
- assert(designMf.includes("'./Button'"), 'Design-system remote must expose Button');
491
- assert(designMf.includes("'./tokens'"), 'Design-system remote must expose tokens');
492
-
493
- const servicePackage = readJson('services/service-recommendations-effect/package.json');
494
- assert(
495
- servicePackage.dependencies?.['@modern-js/runtime'] ===
496
- expectedModernDependency('@modern-js/runtime'),
497
- `Effect service must use @modern-js/runtime through ${expectedModernDependency(
498
- '@modern-js/runtime',
499
- )}`,
500
- );
501
- assert(
502
- servicePackage.devDependencies?.['@modern-js/app-tools'] ===
503
- expectedModernDependency('@modern-js/app-tools'),
504
- `Effect service must use @modern-js/app-tools through ${expectedModernDependency(
505
- '@modern-js/app-tools',
506
- )}`,
507
- );
780
+ const shellPackage = readJson('apps/shell-super-app/package.json');
508
781
  assert(
509
- servicePackage.devDependencies?.['@modern-js/plugin-bff'] ===
782
+ shellPackage.dependencies?.['@modern-js/plugin-bff'] ===
510
783
  expectedModernDependency('@modern-js/plugin-bff'),
511
- `Effect service must use @modern-js/plugin-bff through ${expectedModernDependency(
784
+ `Shell must use @modern-js/plugin-bff through ${expectedModernDependency(
512
785
  '@modern-js/plugin-bff',
513
786
  )}`,
514
787
  );
515
788
  assert(
516
- servicePackage.dependencies?.[`@${packageScope}/shared-effect-api`] === 'workspace:*',
517
- 'Effect service must link generated shared Effect API through workspace:*',
789
+ !shellPackage.dependencies?.[`@${packageScope}/shared-effect-api`],
790
+ 'Shell must not depend on shared-effect-api for default vertical-owned APIs',
518
791
  );
519
792
 
520
- const serviceConfig = readText('services/service-recommendations-effect/modern.config.ts');
793
+ const generatedContract = readJson('.modernjs/ultramodern-generated-contract.json');
521
794
  assert(
522
- serviceConfig.includes("runtimeFramework: 'effect'"),
523
- 'Effect service must use Effect runtime',
795
+ generatedContract.profile === 'cloudflare-ssr-mf-effect-v1',
796
+ 'Generated contract must select the Cloudflare SSR MF Effect profile',
524
797
  );
525
- assert(serviceConfig.includes('bffPlugin()'), 'Effect service must enable bffPlugin');
526
-
527
- const serviceEntry = readText('services/service-recommendations-effect/api/effect/index.ts');
528
798
  assert(
529
- serviceEntry.includes('defineEffectBff'),
530
- 'Effect service must expose defineEffectBff placeholder',
799
+ generatedContract.packageManager?.manager === 'pnpm' &&
800
+ generatedContract.packageManager?.version === '11.3.0' &&
801
+ generatedContract.packageManager?.toolchain === 'mise' &&
802
+ generatedContract.packageManager?.corepack === false,
803
+ 'Generated contract must declare the pnpm 11/mise toolchain policy',
531
804
  );
805
+
806
+ for (const vertical of fullStackVerticals) {
807
+ const contractEntry = generatedContract.apps?.find((app) => app.id === vertical.id);
808
+ assert(
809
+ contractEntry?.deploy?.target === 'cloudflare' &&
810
+ contractEntry?.deploy?.worker?.ssr === true &&
811
+ contractEntry?.deploy?.output?.flat === true &&
812
+ contractEntry?.deploy?.output?.htmlDistPath === './' &&
813
+ contractEntry?.ssr?.mode === 'stream' &&
814
+ contractEntry?.ssr?.moduleFederationAppSSR === true &&
815
+ contractEntry?.i18n?.plugin === '@modern-js/plugin-i18n' &&
816
+ contractEntry?.i18n?.languages?.includes('en') &&
817
+ contractEntry?.i18n?.languages?.includes('cs') &&
818
+ contractEntry?.moduleFederation?.name === vertical.mfName &&
819
+ contractEntry?.moduleFederation?.exposes?.includes('./Widget') &&
820
+ contractEntry?.moduleFederation?.exposes?.includes('./Route') &&
821
+ contractEntry?.moduleFederation?.browserSafeExposesOnly === true &&
822
+ contractEntry?.effect?.runtime === 'effect' &&
823
+ contractEntry?.effect?.import === '@modern-js/plugin-bff/effect-edge' &&
824
+ contractEntry?.effect?.prefix === vertical.apiPrefix &&
825
+ contractEntry?.effect?.workerEntry === 'worker/__modern_bff_effect.js' &&
826
+ contractEntry?.marker?.appId === vertical.id &&
827
+ contractEntry?.marker?.deployProfile === 'cloudflare-ssr-mf-effect-v1' &&
828
+ contractEntry?.marker?.uiSurface === 'ui' &&
829
+ contractEntry?.marker?.apiSurface === 'effect-bff',
830
+ `${vertical.id} generated contract must describe Cloudflare SSR, MF, i18n, Effect BFF, and marker metadata`,
831
+ );
832
+ }
833
+
834
+ const designMf = readText('apps/remotes/remote-design-system/module-federation.config.ts');
835
+ assert(designMf.includes("name: 'remoteDesignSystem'"), 'Design-system remote MF name is missing');
532
836
  assert(
533
- serviceEntry.includes('recommendationsEffectApi'),
534
- 'Effect service must use shared recommendations API',
837
+ designMf.includes("'react-dom/client'"),
838
+ 'Design-system remote MF config must share react-dom/client explicitly',
535
839
  );
840
+ assert(designMf.includes("'./Button'"), 'Design-system remote must expose Button');
841
+ assert(designMf.includes("'./tokens'"), 'Design-system remote must expose tokens');
842
+ const sharedEffectApi = readText('packages/shared-effect-api/src/index.ts');
536
843
  assert(
537
- readText('services/service-recommendations-effect/shared/effect/api.ts').includes('HttpApi.make'),
538
- 'Effect shared API placeholder must define HttpApi',
844
+ !sharedEffectApi.includes('recommendationsEffectApi') &&
845
+ !sharedEffectApi.includes('commerceEffectApi') &&
846
+ !sharedEffectApi.includes('identityEffectApi'),
847
+ 'Shared Effect API package must not own default vertical API contracts',
539
848
  );
540
849
 
541
850
  const topology = readJson('topology/reference-topology.json');
@@ -543,6 +852,20 @@ assert(topology.preset === 'presetUltramodern', 'Topology must reference presetU
543
852
  assert(topology.shell?.id === 'shell-super-app', 'Topology shell id is incorrect');
544
853
  assert(topology.shell?.remoteRefs?.length === 3, 'Topology shell must reference three remotes');
545
854
  assert(topology.remotes?.length === 3, 'Topology must contain three remotes');
855
+ for (const vertical of fullStackVerticals) {
856
+ const topologyEntry = topology.remotes.find((remote) => remote.id === vertical.id);
857
+ assert(topologyEntry?.kind === 'vertical', `${vertical.id} must be a vertical topology entry`);
858
+ assert(
859
+ topologyEntry?.moduleFederation?.manifestUrl?.includes('/mf-manifest.json') &&
860
+ topologyEntry?.api?.effect?.runtime === 'effect' &&
861
+ topologyEntry?.api?.effect?.bff?.prefix === vertical.apiPrefix &&
862
+ topologyEntry?.api?.effect?.bff?.openapi === '/openapi.json' &&
863
+ topologyEntry?.api?.effect?.contract?.export === './shared/effect/api' &&
864
+ topologyEntry?.api?.effect?.client?.export === './effect/client' &&
865
+ topologyEntry?.api?.effect?.serverEntry === `${vertical.path}/api/effect/index.ts`,
866
+ `${vertical.id} topology entry must include both Module Federation and Effect API metadata`,
867
+ );
868
+ }
546
869
  assert(
547
870
  topology.remotes.some(
548
871
  (remote) => remote.id === 'remote-design-system' && remote.kind === 'horizontal-design-system',
@@ -550,8 +873,8 @@ assert(
550
873
  'Topology must contain the horizontal design-system remote',
551
874
  );
552
875
  assert(
553
- topology.effectServices?.[0]?.runtime === 'effect',
554
- 'Topology must contain an Effect service',
876
+ (topology.effectServices ?? []).length === 0,
877
+ 'Default vertical-owned APIs must not be generated as topology.effectServices',
555
878
  );
556
879
  assert(topology.sharedPackages?.length === 3, 'Topology must contain shared package placeholders');
557
880
 
@@ -563,12 +886,12 @@ assert(
563
886
  'Ownership metadata must retain commerce owner',
564
887
  );
565
888
  assert(
566
- ownership.owners?.some(
889
+ !ownership.owners?.some(
567
890
  (owner) =>
568
- owner.id === 'service-recommendations-effect' &&
569
- owner.package === `@${packageScope}/service-recommendations-effect`,
891
+ owner.id === 'service-recommendations-effect' ||
892
+ owner.path === 'services/service-recommendations-effect',
570
893
  ),
571
- 'Ownership metadata must retain Effect service owner',
894
+ 'Ownership metadata must not retain the old default recommendations service owner',
572
895
  );
573
896
 
574
897
  const manifest = readJson('.modernjs/ultramodern-workspace-template-manifest.json');
@@ -588,6 +911,19 @@ assert(
588
911
  baselineAgentSkills.every((skillName) => manifest.agentSkills?.baseline?.includes(skillName)),
589
912
  'Template manifest must list every baseline agent skill',
590
913
  );
914
+ assert(
915
+ manifest.agentSkills?.moduleFederationSource?.repository ===
916
+ 'https://github.com/module-federation/agent-skills' &&
917
+ manifest.agentSkills?.moduleFederationSource?.commit ===
918
+ moduleFederationAgentSkillsCommit,
919
+ 'Template manifest must retain the Module Federation agent skill source',
920
+ );
921
+ assert(
922
+ moduleFederationAgentSkills.every((skillName) =>
923
+ manifest.agentSkills?.moduleFederationSource?.baseline?.includes(skillName),
924
+ ),
925
+ 'Template manifest must list every Module Federation agent skill entry',
926
+ );
591
927
  assert(
592
928
  manifest.agentSkills?.privateSource?.repository === 'https://github.com/TechsioCZ/skills',
593
929
  'Template manifest must retain the private TechsioCZ skill source',