@bleedingdev/modern-js-create 3.2.0-ultramodern.4 → 3.2.0-ultramodern.40

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