@bleedingdev/modern-js-create 3.2.0-ultramodern.21 → 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',
@@ -331,6 +440,26 @@ for (const skillName of baselineAgentSkills) {
331
440
  const privateSource = skillsLock.sources?.find(
332
441
  (source) => source.repository === 'https://github.com/TechsioCZ/skills',
333
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
+ }
334
463
  assert(
335
464
  privateSource?.install === 'clone-if-authorized',
336
465
  'Agent skills lock must configure TechsioCZ skills as clone-if-authorized',
@@ -346,18 +475,52 @@ const appPackagePaths = [
346
475
  'apps/shell-super-app/package.json',
347
476
  'apps/remotes/remote-commerce/package.json',
348
477
  'apps/remotes/remote-identity/package.json',
349
- 'apps/remotes/remote-design-system/package.json',
478
+ `${designSystemRemotePath}/package.json`,
350
479
  ];
351
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
+
352
487
  for (const packagePath of appPackagePaths) {
353
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`);
354
494
  assert(
355
- packageJson.dependencies?.['@modern-js/plugin-i18n'] ===
356
- expectedModernDependency('@modern-js/plugin-i18n'),
357
- `${packagePath} must use @modern-js/plugin-i18n through ${expectedModernDependency(
358
- '@modern-js/plugin-i18n',
359
- )}`,
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`,
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`,
360
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
+ }
361
524
  assert(
362
525
  packageJson.dependencies?.['@modern-js/plugin-tanstack'] ===
363
526
  expectedModernDependency('@modern-js/plugin-tanstack'),
@@ -365,6 +528,13 @@ for (const packagePath of appPackagePaths) {
365
528
  '@modern-js/plugin-tanstack',
366
529
  )}`,
367
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
+ );
368
538
  assert(
369
539
  packageJson.dependencies?.['@modern-js/runtime'] ===
370
540
  expectedModernDependency('@modern-js/runtime'),
@@ -379,6 +549,22 @@ for (const packagePath of appPackagePaths) {
379
549
  '@modern-js/app-tools',
380
550
  )}`,
381
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
+ );
382
568
  assert(
383
569
  packageJson.dependencies?.[`@${packageScope}/shared-contracts`] === 'workspace:*',
384
570
  `${packagePath} must link generated shared contracts through workspace:*`,
@@ -387,29 +573,131 @@ for (const packagePath of appPackagePaths) {
387
573
  packageJson.dependencies?.[`@${packageScope}/shared-design-tokens`] === 'workspace:*',
388
574
  `${packagePath} must link generated shared design tokens through workspace:*`,
389
575
  );
390
- assert(packageJson.dependencies?.i18next === '26.2.0', `${packagePath} must include i18next`);
391
576
  assert(
392
- packageJson.dependencies?.['react-i18next'] === '17.0.8',
393
- `${packagePath} must include react-i18next`,
577
+ packageJson.dependencies?.['node-fetch'] === '^3.3.2',
578
+ `${packagePath} must satisfy the Module Federation SDK node-fetch peer`,
394
579
  );
395
580
  assert(
396
581
  packageJson.dependencies?.['@tanstack/react-router'] === tanstackVersion,
397
582
  `${packagePath} must use @tanstack/react-router ${tanstackVersion}`,
398
583
  );
399
584
  assert(
400
- packageJson.dependencies?.['@module-federation/modern-js-v3'] === '2.4.0',
401
- `${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`,
587
+ );
588
+ assert(
589
+ !packageJson.dependencies?.['react-i18next'],
590
+ `${packagePath} must use native @modern-js/plugin-i18n hooks instead of react-i18next in the TanStack MF starter`,
402
591
  );
403
592
  assert(
404
- packageJson.dependencies?.['zephyr-modernjs-plugin'] === '1.1.1',
405
- `${packagePath} must include the official Zephyr Modern.js plugin`,
593
+ packageJson.dependencies?.['@module-federation/modern-js-v3'] === '2.5.0',
594
+ `${packagePath} must include the Module Federation plugin`,
406
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
+ }
407
641
  assert(
408
642
  packageJson.modernjs?.preset === 'presetUltramodern',
409
643
  `${packagePath} must keep presetUltramodern metadata`,
410
644
  );
411
645
  }
412
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
+
413
701
  for (const configPath of [
414
702
  'apps/shell-super-app/modern.config.ts',
415
703
  'apps/remotes/remote-commerce/modern.config.ts',
@@ -418,26 +706,29 @@ for (const configPath of [
418
706
  ]) {
419
707
  const config = readText(configPath);
420
708
  assert(config.includes('presetUltramodern('), `${configPath} must use presetUltramodern`);
421
- assert(config.includes('i18nPlugin('), `${configPath} must enable plugin-i18n`);
422
- assert(config.includes('localePathRedirect: true'), `${configPath} must prefix localized URLs`);
423
709
  assert(config.includes('ULTRAMODERN_SITE_URL'), `${configPath} must expose site URL metadata`);
424
710
  assert(
425
711
  config.includes('MODERN_PUBLIC_SITE_URL must be set for production builds'),
426
712
  `${configPath} must require MODERN_PUBLIC_SITE_URL for production builds`,
427
713
  );
428
714
  assert(config.includes('tanstackRouterPlugin()'), `${configPath} must enable plugin-tanstack`);
715
+ assert(config.includes('i18nPlugin('), `${configPath} must enable native Modern.js i18n`);
429
716
  assert(
430
- config.includes('moduleFederationPlugin()'),
431
- `${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`,
432
719
  );
433
720
  assert(
434
- config.includes("from 'zephyr-modernjs-plugin'"),
435
- `${configPath} must import the official Zephyr Modern.js plugin`,
721
+ config.includes("publicDir: './locales'"),
722
+ `${configPath} must expose app-owned locale JSON through Modern.js publicDir`,
723
+ );
724
+ assert(
725
+ config.includes('moduleFederationPlugin()'),
726
+ `${configPath} must enable Module Federation`,
436
727
  );
437
- assert(config.includes('withZephyr()'), `${configPath} must enable Zephyr through plugins`);
438
728
  assert(
439
- config.includes("bundler: 'rspack'"),
440
- `${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`,
441
732
  );
442
733
  assert(
443
734
  config.includes("html: './'"),
@@ -464,18 +755,15 @@ for (const routePath of [
464
755
  assert(route.includes('rel="alternate"'), `${routePath} must emit alternate locale metadata`);
465
756
  assert(route.includes('hrefLang="x-default"'), `${routePath} must emit x-default metadata`);
466
757
  assert(route.includes('localizedPath('), `${routePath} must build localized URLs`);
467
- assert(
468
- route.includes('@modern-js/plugin-tanstack/runtime'),
469
- `${routePath} must import TanStack runtime from @modern-js/plugin-tanstack/runtime`,
470
- );
471
- assert(
472
- !route.includes(deprecatedTanstackRuntime),
473
- `${routePath} must not import deprecated TanStack runtime path`,
474
- );
758
+ assert(route.includes("import '../index.css';"), `${routePath} must import route CSS`);
475
759
  }
476
760
 
477
761
  const shellMf = readText('apps/shell-super-app/module-federation.config.ts');
478
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
+ );
479
767
  assert(
480
768
  shellMf.includes('remoteCommerce@http://localhost:3021/mf-manifest.json'),
481
769
  'Shell must reference commerce remote',
@@ -489,61 +777,74 @@ assert(
489
777
  'Shell must reference design-system remote',
490
778
  );
491
779
 
492
- const commerceMf = readText('apps/remotes/remote-commerce/module-federation.config.ts');
493
- assert(commerceMf.includes("name: 'remoteCommerce'"), 'Commerce remote MF name is missing');
494
- assert(commerceMf.includes("'./Widget'"), 'Commerce remote must expose a widget');
495
-
496
- const designMf = readText('apps/remotes/remote-design-system/module-federation.config.ts');
497
- assert(designMf.includes("name: 'remoteDesignSystem'"), 'Design-system remote MF name is missing');
498
- assert(designMf.includes("'./Button'"), 'Design-system remote must expose Button');
499
- assert(designMf.includes("'./tokens'"), 'Design-system remote must expose tokens');
500
-
501
- const servicePackage = readJson('services/service-recommendations-effect/package.json');
502
- assert(
503
- servicePackage.dependencies?.['@modern-js/runtime'] ===
504
- expectedModernDependency('@modern-js/runtime'),
505
- `Effect service must use @modern-js/runtime through ${expectedModernDependency(
506
- '@modern-js/runtime',
507
- )}`,
508
- );
780
+ const shellPackage = readJson('apps/shell-super-app/package.json');
509
781
  assert(
510
- servicePackage.devDependencies?.['@modern-js/app-tools'] ===
511
- expectedModernDependency('@modern-js/app-tools'),
512
- `Effect service must use @modern-js/app-tools through ${expectedModernDependency(
513
- '@modern-js/app-tools',
514
- )}`,
515
- );
516
- assert(
517
- servicePackage.devDependencies?.['@modern-js/plugin-bff'] ===
782
+ shellPackage.dependencies?.['@modern-js/plugin-bff'] ===
518
783
  expectedModernDependency('@modern-js/plugin-bff'),
519
- `Effect service must use @modern-js/plugin-bff through ${expectedModernDependency(
784
+ `Shell must use @modern-js/plugin-bff through ${expectedModernDependency(
520
785
  '@modern-js/plugin-bff',
521
786
  )}`,
522
787
  );
523
788
  assert(
524
- servicePackage.dependencies?.[`@${packageScope}/shared-effect-api`] === 'workspace:*',
525
- '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',
526
791
  );
527
792
 
528
- const serviceConfig = readText('services/service-recommendations-effect/modern.config.ts');
793
+ const generatedContract = readJson('.modernjs/ultramodern-generated-contract.json');
529
794
  assert(
530
- serviceConfig.includes("runtimeFramework: 'effect'"),
531
- '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',
532
797
  );
533
- assert(serviceConfig.includes('bffPlugin()'), 'Effect service must enable bffPlugin');
534
-
535
- const serviceEntry = readText('services/service-recommendations-effect/api/effect/index.ts');
536
798
  assert(
537
- serviceEntry.includes('defineEffectBff'),
538
- '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',
539
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');
540
836
  assert(
541
- serviceEntry.includes('recommendationsEffectApi'),
542
- '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',
543
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');
544
843
  assert(
545
- readText('services/service-recommendations-effect/shared/effect/api.ts').includes('HttpApi.make'),
546
- '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',
547
848
  );
548
849
 
549
850
  const topology = readJson('topology/reference-topology.json');
@@ -551,6 +852,20 @@ assert(topology.preset === 'presetUltramodern', 'Topology must reference presetU
551
852
  assert(topology.shell?.id === 'shell-super-app', 'Topology shell id is incorrect');
552
853
  assert(topology.shell?.remoteRefs?.length === 3, 'Topology shell must reference three remotes');
553
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
+ }
554
869
  assert(
555
870
  topology.remotes.some(
556
871
  (remote) => remote.id === 'remote-design-system' && remote.kind === 'horizontal-design-system',
@@ -558,8 +873,8 @@ assert(
558
873
  'Topology must contain the horizontal design-system remote',
559
874
  );
560
875
  assert(
561
- topology.effectServices?.[0]?.runtime === 'effect',
562
- 'Topology must contain an Effect service',
876
+ (topology.effectServices ?? []).length === 0,
877
+ 'Default vertical-owned APIs must not be generated as topology.effectServices',
563
878
  );
564
879
  assert(topology.sharedPackages?.length === 3, 'Topology must contain shared package placeholders');
565
880
 
@@ -571,12 +886,12 @@ assert(
571
886
  'Ownership metadata must retain commerce owner',
572
887
  );
573
888
  assert(
574
- ownership.owners?.some(
889
+ !ownership.owners?.some(
575
890
  (owner) =>
576
- owner.id === 'service-recommendations-effect' &&
577
- owner.package === `@${packageScope}/service-recommendations-effect`,
891
+ owner.id === 'service-recommendations-effect' ||
892
+ owner.path === 'services/service-recommendations-effect',
578
893
  ),
579
- 'Ownership metadata must retain Effect service owner',
894
+ 'Ownership metadata must not retain the old default recommendations service owner',
580
895
  );
581
896
 
582
897
  const manifest = readJson('.modernjs/ultramodern-workspace-template-manifest.json');
@@ -596,6 +911,19 @@ assert(
596
911
  baselineAgentSkills.every((skillName) => manifest.agentSkills?.baseline?.includes(skillName)),
597
912
  'Template manifest must list every baseline agent skill',
598
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
+ );
599
927
  assert(
600
928
  manifest.agentSkills?.privateSource?.repository === 'https://github.com/TechsioCZ/skills',
601
929
  'Template manifest must retain the private TechsioCZ skill source',