@bleedingdev/modern-js-create 3.2.0-ultramodern.5 → 3.2.0-ultramodern.51

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