@bleedingdev/modern-js-create 3.2.0-ultramodern.13 → 3.2.0-ultramodern.14

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.
package/dist/index.js CHANGED
@@ -1416,7 +1416,7 @@ const LocalizedHead = () => {
1416
1416
  function createShellPage() {
1417
1417
  return `import { Helmet } from '@modern-js/runtime/head';
1418
1418
  import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
1419
- import { useLocation } from '@modern-js/runtime/tanstack-router';
1419
+ import { useLocation } from '@modern-js/plugin-tanstack/runtime';
1420
1420
  import { useTranslation } from 'react-i18next';
1421
1421
 
1422
1422
  const remotes = ['remote-commerce', 'remote-identity', 'remote-design-system'];
@@ -1462,7 +1462,7 @@ export default function ShellHome() {
1462
1462
  function createRemotePage(app) {
1463
1463
  return `import { Helmet } from '@modern-js/runtime/head';
1464
1464
  import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
1465
- import { useLocation } from '@modern-js/runtime/tanstack-router';
1465
+ import { useLocation } from '@modern-js/plugin-tanstack/runtime';
1466
1466
  import { useTranslation } from 'react-i18next';
1467
1467
 
1468
1468
  ${createLocalizedHeadComponent()}
@@ -2028,7 +2028,6 @@ const templateIdPattern = /^[a-z0-9][a-z0-9._-]*$/;
2028
2028
  const packageNamePattern = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
2029
2029
  const requiredDeniedPaths = [
2030
2030
  '.git/**',
2031
- '.github/**',
2032
2031
  '.npmrc',
2033
2032
  '.yarnrc',
2034
2033
  '.env',
@@ -2211,6 +2210,7 @@ function createBuiltinTemplateManifest(version) {
2211
2210
  allowedPaths: [
2212
2211
  '.agents/**',
2213
2212
  '.browserslistrc',
2213
+ '.github/**',
2214
2214
  '.gitignore',
2215
2215
  '.modernjs/**',
2216
2216
  '.nvmrc',
@@ -2223,10 +2223,12 @@ function createBuiltinTemplateManifest(version) {
2223
2223
  'oxlint.config.ts',
2224
2224
  'package.json',
2225
2225
  'postcss.config.mjs',
2226
+ 'rstest.config.mts',
2226
2227
  "scripts/**",
2227
2228
  'shared/**',
2228
2229
  'src/**',
2229
2230
  'tailwind.config.ts',
2231
+ 'tests/**',
2230
2232
  'tsconfig.json'
2231
2233
  ],
2232
2234
  deniedPaths: requiredDeniedPaths,
@@ -2255,10 +2257,13 @@ function createBuiltinTemplateManifest(version) {
2255
2257
  postMaterializationValidation: [
2256
2258
  'ultramodern-contract-check',
2257
2259
  'dependency-install-with-lifecycle-deny',
2260
+ 'package-source-retained',
2261
+ 'rstest-smoke-tests',
2258
2262
  'template-manifest-retained'
2259
2263
  ],
2260
2264
  expectedCommands: [
2261
2265
  "pnpm install --ignore-scripts",
2266
+ 'pnpm test',
2262
2267
  'pnpm run ultramodern:check'
2263
2268
  ]
2264
2269
  }
@@ -2487,6 +2492,45 @@ function singleAppModernPackageSpecifier(packageName, packageSource, useWorkspac
2487
2492
  if ('install' !== packageSource.strategy || !packageSource.aliasScope) return packageSource.modernPackageVersion;
2488
2493
  return `npm:${src_modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
2489
2494
  }
2495
+ const singleAppModernPackages = [
2496
+ '@modern-js/runtime',
2497
+ '@modern-js/app-tools',
2498
+ '@modern-js/tsconfig',
2499
+ '@modern-js/plugin-i18n',
2500
+ '@modern-js/plugin-tanstack',
2501
+ '@modern-js/plugin-bff',
2502
+ '@modern-js/adapter-rstest'
2503
+ ];
2504
+ function createSingleAppPackageSourceEvidence(packageSource, useWorkspaceProtocol) {
2505
+ const strategy = useWorkspaceProtocol ? 'workspace' : 'install';
2506
+ const specifier = useWorkspaceProtocol ? 'workspace:*' : packageSource.modernPackageVersion;
2507
+ const aliases = 'install' === strategy && packageSource.aliasScope ? Object.fromEntries(singleAppModernPackages.map((packageName)=>[
2508
+ packageName,
2509
+ src_modernAliasPackageName(packageName, packageSource)
2510
+ ])) : void 0;
2511
+ return {
2512
+ schemaVersion: 1,
2513
+ preset: 'presetUltramodern',
2514
+ strategy,
2515
+ modernPackages: {
2516
+ specifier,
2517
+ packages: singleAppModernPackages,
2518
+ ...packageSource.registry ? {
2519
+ registry: packageSource.registry
2520
+ } : {},
2521
+ ...aliases ? {
2522
+ aliases
2523
+ } : {}
2524
+ }
2525
+ };
2526
+ }
2527
+ function writeSingleAppPackageSourceEvidence(targetDir, packageSource, useWorkspaceProtocol) {
2528
+ const evidencePath = node_path.join(targetDir, '.modernjs', 'ultramodern-package-source.json');
2529
+ node_fs.mkdirSync(node_path.dirname(evidencePath), {
2530
+ recursive: true
2531
+ });
2532
+ node_fs.writeFileSync(evidencePath, `${JSON.stringify(createSingleAppPackageSourceEvidence(packageSource, useWorkspaceProtocol), null, 2)}\n`);
2533
+ }
2490
2534
  function isDirectoryEmpty(dirPath) {
2491
2535
  if (!node_fs.existsSync(dirPath)) return false;
2492
2536
  try {
@@ -2609,6 +2653,7 @@ async function main() {
2609
2653
  version: useWorkspaceProtocol ? 'workspace:*' : packageSource.modernPackageVersion,
2610
2654
  runtimeVersion: singleAppModernPackageSpecifier('@modern-js/runtime', packageSource, useWorkspaceProtocol),
2611
2655
  appToolsVersion: singleAppModernPackageSpecifier('@modern-js/app-tools', packageSource, useWorkspaceProtocol),
2656
+ adapterRstestVersion: singleAppModernPackageSpecifier('@modern-js/adapter-rstest', packageSource, useWorkspaceProtocol),
2612
2657
  tsconfigVersion: singleAppModernPackageSpecifier('@modern-js/tsconfig', packageSource, useWorkspaceProtocol),
2613
2658
  pluginTanstackVersion: singleAppModernPackageSpecifier('@modern-js/plugin-tanstack', packageSource, useWorkspaceProtocol),
2614
2659
  pluginBffVersion: singleAppModernPackageSpecifier('@modern-js/plugin-bff', packageSource, useWorkspaceProtocol),
@@ -2622,6 +2667,14 @@ async function main() {
2622
2667
  const targetPackageJson = node_path.join(targetDir, 'package.json');
2623
2668
  const packageJson = JSON.parse(node_fs.readFileSync(targetPackageJson, 'utf-8'));
2624
2669
  packageJson.name = generatedPackageName;
2670
+ packageJson.modernjs = {
2671
+ ...packageJson.modernjs ?? {},
2672
+ preset: 'presetUltramodern',
2673
+ packageSource: {
2674
+ strategy: useWorkspaceProtocol ? 'workspace' : 'install',
2675
+ config: './.modernjs/ultramodern-package-source.json'
2676
+ }
2677
+ };
2625
2678
  if (isSubproject) {
2626
2679
  delete packageJson['lint-staged'];
2627
2680
  delete packageJson['simple-git-hooks'];
@@ -2644,6 +2697,7 @@ async function main() {
2644
2697
  }
2645
2698
  node_fs.writeFileSync(targetPackageJson, `${JSON.stringify(packageJson, null, 2)}\n`);
2646
2699
  writeTemplateManifestEvidence(targetDir, templateManifest);
2700
+ writeSingleAppPackageSourceEvidence(targetDir, packageSource, useWorkspaceProtocol);
2647
2701
  const dim = '\x1b[2m\x1b[3m';
2648
2702
  const reset = '\x1b[0m';
2649
2703
  console.log(`${i18n.t(localeKeys.message.success)}\n`);
@@ -2660,6 +2714,7 @@ function copyTemplate(src, dest, options) {
2660
2714
  });
2661
2715
  const excludeInSubproject = [
2662
2716
  '.agents',
2717
+ '.github',
2663
2718
  '.gitignore.handlebars',
2664
2719
  'AGENTS.md',
2665
2720
  '.npmrc',
@@ -2689,6 +2744,7 @@ function copyTemplate(src, dest, options) {
2689
2744
  version: options.version,
2690
2745
  runtimeVersion: options.runtimeVersion,
2691
2746
  appToolsVersion: options.appToolsVersion,
2747
+ adapterRstestVersion: options.adapterRstestVersion,
2692
2748
  tsconfigVersion: options.tsconfigVersion,
2693
2749
  pluginTanstackVersion: options.pluginTanstackVersion,
2694
2750
  pluginBffVersion: options.pluginBffVersion,
@@ -2700,7 +2756,7 @@ function copyTemplate(src, dest, options) {
2700
2756
  useHonoBff: 'hono' === options.bffRuntime,
2701
2757
  bffRuntime: options.bffRuntime,
2702
2758
  enableTailwind: options.enableTailwind,
2703
- routerImportPath: 'tanstack' === options.routerFramework ? 'tanstack-router' : 'router'
2759
+ routerRuntimeImport: 'tanstack' === options.routerFramework ? '@modern-js/plugin-tanstack/runtime' : '@modern-js/runtime/router'
2704
2760
  });
2705
2761
  if (0 === rendered.trim().length) continue;
2706
2762
  destPath = destPath.replace(/\.handlebars$/, '');
package/package.json CHANGED
@@ -21,7 +21,7 @@
21
21
  "engines": {
22
22
  "node": ">=20"
23
23
  },
24
- "version": "3.2.0-ultramodern.13",
24
+ "version": "3.2.0-ultramodern.14",
25
25
  "types": "./dist/types/index.d.ts",
26
26
  "main": "./dist/index.js",
27
27
  "bin": {
@@ -41,7 +41,7 @@
41
41
  "@types/node": "^25.8.0",
42
42
  "@typescript/native-preview": "7.0.0-dev.20260516.1",
43
43
  "tsx": "^4.22.0",
44
- "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.13"
44
+ "@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.14"
45
45
  },
46
46
  "publishConfig": {
47
47
  "registry": "https://registry.npmjs.org/",
@@ -54,6 +54,6 @@
54
54
  "start": "node ./dist/index.js"
55
55
  },
56
56
  "ultramodern": {
57
- "frameworkVersion": "3.2.0-ultramodern.13"
57
+ "frameworkVersion": "3.2.0-ultramodern.14"
58
58
  }
59
59
  }
@@ -21,7 +21,7 @@ jobs:
21
21
  cache: pnpm
22
22
 
23
23
  - name: Install Dependencies
24
- run: pnpm install
24
+ run: pnpm install --ignore-scripts
25
25
 
26
26
  - name: Validate Ultramodern Contract
27
27
  run: pnpm run ultramodern:check
@@ -1,17 +1,22 @@
1
1
  {
2
2
  "name": "{{packageName}}",
3
3
  "version": "0.1.0",
4
+ "private": true,
4
5
  "type": "module",
6
+ "packageManager": "pnpm@11.1.2",
5
7
  "scripts": {
6
8
  "reset": "npx rimraf node_modules ./**/node_modules",
7
9
  "dev": "modern dev",
8
10
  "build": "modern build",
9
11
  "serve": "modern serve",
12
+ "test": "rstest run",
10
13
  "typecheck": "node -e \"const fs = require('node:fs'); const { execFileSync, spawnSync } = require('node:child_process'); const bin = execFileSync('effect-tsgo', ['get-exe-path'], { encoding: 'utf8' }).trim(); if (process.platform !== 'win32') fs.chmodSync(bin, 0o755); const result = spawnSync(bin, ['--noEmit', '-p', 'tsconfig.json'], { stdio: 'inherit' }); process.exit(result.status ?? 1);\"",
11
14
  "i18n:check": "node ./scripts/check-i18n-strings.mjs",
15
+ {{#unless isSubproject}}
12
16
  "skills:install": "node ./scripts/bootstrap-agent-skills.mjs",
13
17
  "skills:check": "node ./scripts/bootstrap-agent-skills.mjs --check",
14
- "ultramodern:check": "{{#unless isSubproject}}pnpm format:check && pnpm lint && {{/unless}}pnpm typecheck && pnpm i18n:check{{#unless isSubproject}} && pnpm skills:check{{/unless}} && node ./scripts/validate-ultramodern.mjs"{{#unless isSubproject}},
18
+ {{/unless}}
19
+ "ultramodern:check": "{{#unless isSubproject}}pnpm format:check && pnpm lint && {{/unless}}pnpm typecheck && pnpm i18n:check && pnpm test{{#unless isSubproject}} && pnpm skills:check{{/unless}} && node ./scripts/validate-ultramodern.mjs"{{#unless isSubproject}},
15
20
  "format": "oxfmt .",
16
21
  "format:check": "oxfmt --check .",
17
22
  "lint": "oxlint .",
@@ -32,16 +37,20 @@
32
37
  },
33
38
  "devDependencies": {
34
39
  "@effect/tsgo": "0.7.3",
40
+ "@modern-js/adapter-rstest": "{{adapterRstestVersion}}",
35
41
  "@modern-js/app-tools": "{{appToolsVersion}}",
36
42
  {{#if enableBff}} "@modern-js/plugin-bff": "{{pluginBffVersion}}",
37
43
  {{/if}} "@modern-js/tsconfig": "{{tsconfigVersion}}",
44
+ "@rstest/core": "0.10.0",
38
45
  {{#if enableTailwind}}
39
46
  "@tailwindcss/postcss": "^4.1.18",
40
47
  {{/if}}
48
+ "@testing-library/jest-dom": "^6.9.1",
41
49
  "@types/node": "^20",
42
50
  "@types/react": "^19.1.8",
43
51
  "@types/react-dom": "^19.1.6",
44
52
  "@typescript/native-preview": "7.0.0-dev.20260518.1",
53
+ "happy-dom": "^20.8.9",
45
54
  {{#unless isSubproject}}
46
55
  "lint-staged": "~15.4.0",
47
56
  "oxfmt": "0.50.0",
@@ -62,6 +71,15 @@
62
71
  ]
63
72
  }{{/unless}},
64
73
  "engines": {
65
- "node": ">=20"
74
+ "node": ">=20",
75
+ "pnpm": ">=11.0.0"
76
+ },
77
+ "pnpm": {
78
+ "onlyBuiltDependencies": [
79
+ "@swc/core",
80
+ "core-js",
81
+ "esbuild",
82
+ "msgpackr-extract"
83
+ ]
66
84
  }
67
85
  }
@@ -0,0 +1,8 @@
1
+ import { withModernConfig } from '@modern-js/adapter-rstest';
2
+ import { defineConfig } from '@rstest/core';
3
+
4
+ export default defineConfig({
5
+ extends: withModernConfig(),
6
+ testEnvironment: 'happy-dom',
7
+ setupFiles: ['./tests/rstest.setup.ts'],
8
+ });
@@ -3,6 +3,11 @@ import path from 'node:path';
3
3
 
4
4
  const configPath = path.resolve(process.cwd(), 'modern.config.ts');
5
5
  const templateManifestPath = path.resolve(process.cwd(), '.modernjs/mv-template-manifest.json');
6
+ const packageSourcePath = path.resolve(
7
+ process.cwd(),
8
+ '.modernjs/ultramodern-package-source.json',
9
+ );
10
+ const isSubproject = {{isSubproject}};
6
11
 
7
12
  if (!fs.existsSync(configPath)) {
8
13
  console.error('modern.config.ts not found');
@@ -37,7 +42,6 @@ if (!fs.existsSync(templateManifestPath)) {
37
42
  const templateManifest = JSON.parse(fs.readFileSync(templateManifestPath, 'utf-8'));
38
43
  const requiredDeniedPaths = [
39
44
  '.git/**',
40
- '.github/**',
41
45
  '.npmrc',
42
46
  '.yarnrc',
43
47
  '.env',
@@ -48,19 +52,28 @@ const requiredDeniedPaths = [
48
52
  const requiredPostMaterialization = [
49
53
  'ultramodern-contract-check',
50
54
  'dependency-install-with-lifecycle-deny',
55
+ 'package-source-retained',
56
+ 'rstest-smoke-tests',
51
57
  'template-manifest-retained',
52
58
  ];
53
59
  const requiredPaths = [
60
+ {{#unless isSubproject}}
54
61
  'AGENTS.md',
55
62
  '.agents/skills-lock.json',
63
+ '.github/workflows/ultramodern-gates.yml',
56
64
  'oxlint.config.ts',
57
65
  'oxfmt.config.ts',
58
66
  'scripts/bootstrap-agent-skills.mjs',
67
+ {{/unless}}
68
+ '.modernjs/ultramodern-package-source.json',
69
+ 'rstest.config.mts',
59
70
  'scripts/check-i18n-strings.mjs',
60
71
  'config/public/locales/en/translation.json',
61
72
  'config/public/locales/cs/translation.json',
62
73
  'src/modern-app-env.d.ts',
63
74
  'src/routes/[lang]/page.tsx',
75
+ 'tests/rstest.setup.ts',
76
+ 'tests/ultramodern.contract.test.ts',
64
77
  ];
65
78
  const manifestErrors = [];
66
79
 
@@ -86,12 +99,22 @@ for (const token of [
86
99
  'hrefLang="x-default"',
87
100
  'localizedPath(',
88
101
  '<a',
102
+ {{#if isTanstackRouter}}
103
+ "@modern-js/plugin-tanstack/runtime",
104
+ {{/if}}
89
105
  ]) {
90
106
  if (!pageContent.includes(token)) {
91
107
  console.error(`Localized route is missing ${token}`);
92
108
  process.exit(1);
93
109
  }
94
110
  }
111
+ {{#if isTanstackRouter}}
112
+ const deprecatedTanstackRuntime = '@modern-js/runtime/' + 'tanstack-router';
113
+ if (pageContent.includes(deprecatedTanstackRuntime)) {
114
+ console.error('Localized route must import TanStack runtime from @modern-js/plugin-tanstack/runtime');
115
+ process.exit(1);
116
+ }
117
+ {{/if}}
95
118
 
96
119
  if (templateManifest.schemaVersion !== 1) {
97
120
  manifestErrors.push('schemaVersion');
@@ -139,22 +162,32 @@ if (manifestErrors.length > 0) {
139
162
  const packageJson = JSON.parse(
140
163
  fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),
141
164
  );
165
+ const packageSource = JSON.parse(fs.readFileSync(packageSourcePath, 'utf-8'));
142
166
  const unresolvedTemplateMarker = String.fromCodePoint(123, 123);
143
167
  if (JSON.stringify(packageJson).includes(unresolvedTemplateMarker)) {
144
168
  console.error('package.json contains unresolved template markers');
145
169
  process.exit(1);
146
170
  }
171
+ if (JSON.stringify(packageSource).includes(unresolvedTemplateMarker)) {
172
+ console.error('package source metadata contains unresolved template markers');
173
+ process.exit(1);
174
+ }
175
+ {{#unless isSubproject}}
147
176
  const skillsLock = JSON.parse(
148
177
  fs.readFileSync(path.resolve(process.cwd(), '.agents/skills-lock.json'), 'utf-8'),
149
178
  );
179
+ {{/unless}}
150
180
  const requiredScripts = {
181
+ 'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
182
+ test: 'rstest run',
183
+ {{#unless isSubproject}}
151
184
  format: 'oxfmt .',
152
185
  'format:check': 'oxfmt --check .',
153
- 'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
154
186
  lint: 'oxlint .',
155
187
  'lint:fix': 'oxlint . --fix',
156
188
  'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
157
189
  'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
190
+ {{/unless}}
158
191
  };
159
192
 
160
193
  for (const [scriptName, scriptCommand] of Object.entries(requiredScripts)) {
@@ -172,6 +205,112 @@ if (
172
205
  process.exit(1);
173
206
  }
174
207
 
208
+ if (!packageJson.scripts?.['ultramodern:check']?.includes('pnpm test')) {
209
+ console.error('ultramodern:check must run the generated Rstest suite');
210
+ process.exit(1);
211
+ }
212
+
213
+ if (packageJson.private !== true) {
214
+ console.error('Generated app package must be private by default');
215
+ process.exit(1);
216
+ }
217
+
218
+ if (packageJson.packageManager !== 'pnpm@11.1.2') {
219
+ console.error('Generated app package must pin pnpm@11.1.2');
220
+ process.exit(1);
221
+ }
222
+
223
+ if (packageJson.engines?.pnpm !== '>=11.0.0') {
224
+ console.error('Generated app package must require pnpm >=11.0.0');
225
+ process.exit(1);
226
+ }
227
+
228
+ if (packageJson.modernjs?.preset !== 'presetUltramodern') {
229
+ console.error('package.json must declare presetUltramodern metadata');
230
+ process.exit(1);
231
+ }
232
+
233
+ if (
234
+ packageJson.modernjs?.packageSource?.config !== './.modernjs/ultramodern-package-source.json'
235
+ ) {
236
+ console.error('package.json must retain package source metadata location');
237
+ process.exit(1);
238
+ }
239
+
240
+ if (packageSource.schemaVersion !== 1) {
241
+ console.error('Package source metadata must use schemaVersion 1');
242
+ process.exit(1);
243
+ }
244
+
245
+ if (packageSource.preset !== 'presetUltramodern') {
246
+ console.error('Package source metadata must declare presetUltramodern');
247
+ process.exit(1);
248
+ }
249
+
250
+ if (packageSource.strategy !== 'workspace' && packageSource.strategy !== 'install') {
251
+ console.error('Package source strategy must be workspace or install');
252
+ process.exit(1);
253
+ }
254
+
255
+ const expectedModernPackages = [
256
+ '@modern-js/runtime',
257
+ '@modern-js/app-tools',
258
+ '@modern-js/tsconfig',
259
+ '@modern-js/plugin-i18n',
260
+ '@modern-js/plugin-tanstack',
261
+ '@modern-js/plugin-bff',
262
+ '@modern-js/adapter-rstest',
263
+ ];
264
+
265
+ for (const packageName of expectedModernPackages) {
266
+ if (!packageSource.modernPackages?.packages?.includes(packageName)) {
267
+ console.error(`Package source metadata must include ${packageName}`);
268
+ process.exit(1);
269
+ }
270
+ }
271
+
272
+ const expectedModernSpecifier = packageSource.modernPackages?.specifier;
273
+ if (typeof expectedModernSpecifier !== 'string' || expectedModernSpecifier.length === 0) {
274
+ console.error('Package source metadata must provide a Modern package specifier');
275
+ process.exit(1);
276
+ }
277
+
278
+ const expectedModernDependency = (packageName) => {
279
+ const alias = packageSource.modernPackages?.aliases?.[packageName];
280
+ return typeof alias === 'string'
281
+ ? `npm:${alias}@${expectedModernSpecifier}`
282
+ : expectedModernSpecifier;
283
+ };
284
+
285
+ for (const packageName of [
286
+ '@modern-js/runtime',
287
+ '@modern-js/plugin-i18n',
288
+ '@modern-js/plugin-tanstack',
289
+ ]) {
290
+ if (
291
+ packageJson.dependencies?.[packageName] &&
292
+ packageJson.dependencies[packageName] !== expectedModernDependency(packageName)
293
+ ) {
294
+ console.error(`Dependency ${packageName} must match package source metadata`);
295
+ process.exit(1);
296
+ }
297
+ }
298
+
299
+ for (const packageName of [
300
+ '@modern-js/app-tools',
301
+ '@modern-js/adapter-rstest',
302
+ '@modern-js/tsconfig',
303
+ '@modern-js/plugin-bff',
304
+ ]) {
305
+ if (
306
+ packageJson.devDependencies?.[packageName] &&
307
+ packageJson.devDependencies[packageName] !== expectedModernDependency(packageName)
308
+ ) {
309
+ console.error(`Dev dependency ${packageName} must match package source metadata`);
310
+ process.exit(1);
311
+ }
312
+ }
313
+
175
314
  for (const dependency of ['@modern-js/plugin-i18n', 'i18next', 'react-i18next']) {
176
315
  if (!packageJson.dependencies?.[dependency]) {
177
316
  console.error(`Missing dependency: ${dependency}`);
@@ -181,10 +320,16 @@ for (const dependency of ['@modern-js/plugin-i18n', 'i18next', 'react-i18next'])
181
320
 
182
321
  for (const dependency of [
183
322
  '@effect/tsgo',
323
+ '@modern-js/adapter-rstest',
324
+ '@rstest/core',
325
+ '@testing-library/jest-dom',
184
326
  '@typescript/native-preview',
327
+ 'happy-dom',
328
+ {{#unless isSubproject}}
185
329
  'oxlint',
186
330
  'oxfmt',
187
331
  'ultracite',
332
+ {{/unless}}
188
333
  ]) {
189
334
  if (!packageJson.devDependencies?.[dependency]) {
190
335
  console.error(`Missing devDependency: ${dependency}`);
@@ -192,6 +337,7 @@ for (const dependency of [
192
337
  }
193
338
  }
194
339
 
340
+ {{#unless isSubproject}}
195
341
  const privateSource = skillsLock.sources?.find(
196
342
  (source) => source.repository === 'https://github.com/TechsioCZ/skills',
197
343
  );
@@ -202,5 +348,6 @@ for (const skillName of ['plan-graph', 'dag', 'subagent-graph', 'helm', 'debugge
202
348
  process.exit(1);
203
349
  }
204
350
  }
351
+ {{/unless}}
205
352
 
206
353
  console.log('Ultramodern contract check passed.');
@@ -1,6 +1,6 @@
1
1
  import { Helmet } from '@modern-js/runtime/head';
2
2
  import { useModernI18n } from '@modern-js/plugin-i18n/runtime';
3
- import { useLocation } from '@modern-js/runtime/{{routerImportPath}}';
3
+ import { useLocation } from '{{routerRuntimeImport}}';
4
4
  {{#if useEffectBff}}import effectBff from '@api/effect/index';
5
5
  import { Effect } from '@modern-js/plugin-bff/effect-client';
6
6
  import { useEffect, useState } from 'react';
@@ -1,4 +1,4 @@
1
- import { Outlet } from '@modern-js/runtime/{{routerImportPath}}';
1
+ import { Outlet } from '{{routerRuntimeImport}}';
2
2
 
3
3
  export default function Layout() {
4
4
  return (
@@ -0,0 +1,4 @@
1
+ import { expect } from '@rstest/core';
2
+ import * as jestDomMatchers from '@testing-library/jest-dom/matchers';
3
+
4
+ expect.extend(jestDomMatchers);
@@ -0,0 +1,7 @@
1
+ {
2
+ "extends": "../tsconfig.json",
3
+ "compilerOptions": {
4
+ "types": ["@testing-library/jest-dom", "@rstest/core/globals"]
5
+ },
6
+ "include": ["./"]
7
+ }
@@ -0,0 +1,67 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { describe, expect, test } from '@rstest/core';
4
+
5
+ const root = process.cwd();
6
+ const readText = (relativePath: string) =>
7
+ fs.readFileSync(path.join(root, relativePath), 'utf-8');
8
+ const readJson = <T>(relativePath: string): T =>
9
+ JSON.parse(readText(relativePath)) as T;
10
+
11
+ describe('generated UltraModern contract', () => {
12
+ test('keeps localized route metadata and Rstest wiring', () => {
13
+ expect(fs.existsSync(path.join(root, 'src/routes/[lang]/page.tsx'))).toBe(
14
+ true,
15
+ );
16
+ expect(fs.existsSync(path.join(root, 'src/routes/page.tsx'))).toBe(false);
17
+
18
+ const page = readText('src/routes/[lang]/page.tsx');
19
+ expect(page).toContain('rel="canonical"');
20
+ expect(page).toContain('rel="alternate"');
21
+ expect(page).toContain('hrefLang="x-default"');
22
+ expect(page).toContain('localizedPath(');
23
+
24
+ const config = readText('rstest.config.mts');
25
+ expect(config).toContain("withModernConfig()");
26
+ expect(config).toContain("testEnvironment: 'happy-dom'");
27
+ });
28
+
29
+ test('retains package-source metadata for generated Modern.js packages', () => {
30
+ const packageJson = readJson<{
31
+ dependencies?: Record<string, string>;
32
+ devDependencies?: Record<string, string>;
33
+ modernjs?: {
34
+ packageSource?: {
35
+ config?: string;
36
+ };
37
+ preset?: string;
38
+ };
39
+ }>('package.json');
40
+ const packageSource = readJson<{
41
+ modernPackages?: {
42
+ packages?: string[];
43
+ specifier?: string;
44
+ };
45
+ strategy?: string;
46
+ }>('.modernjs/ultramodern-package-source.json');
47
+
48
+ expect(packageJson.modernjs?.preset).toBe('presetUltramodern');
49
+ expect(packageJson.modernjs?.packageSource?.config).toBe(
50
+ './.modernjs/ultramodern-package-source.json',
51
+ );
52
+ expect(packageSource.strategy).toMatch(/^(workspace|install)$/u);
53
+ expect(packageSource.modernPackages?.packages).toContain(
54
+ '@modern-js/runtime',
55
+ );
56
+ expect(packageSource.modernPackages?.packages).toContain(
57
+ '@modern-js/app-tools',
58
+ );
59
+ expect(packageSource.modernPackages?.packages).toContain(
60
+ '@modern-js/adapter-rstest',
61
+ );
62
+ expect(packageSource.modernPackages?.specifier).toBeTruthy();
63
+ expect(
64
+ packageJson.devDependencies?.['@modern-js/adapter-rstest'],
65
+ ).toBeTruthy();
66
+ });
67
+ });
@@ -120,6 +120,8 @@ for (const appDirectory of [
120
120
  const rootPackage = readJson('package.json');
121
121
  const packageSource = readJson('.modernjs/ultramodern-package-source.json');
122
122
  const skillsLock = readJson('.agents/skills-lock.json');
123
+ const pnpmWorkspace = readText('pnpm-workspace.yaml');
124
+ const deprecatedTanstackRuntime = '@modern-js/runtime/' + 'tanstack-router';
123
125
  const expectedModernSpecifier =
124
126
  packageSource.strategy === 'install' ? packageSource.modernPackages?.specifier : 'workspace:*';
125
127
 
@@ -138,6 +140,16 @@ assert(rootPackage.private === true, 'Root package must be private');
138
140
  assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare presetUltramodern');
139
141
  assert(rootPackage.packageManager === 'pnpm@11.1.2', 'Root must pin pnpm 11.1.2');
140
142
  assert(rootPackage.engines?.pnpm === '>=11.0.0', 'Root must require pnpm >=11');
143
+ for (const requiredSnippet of [
144
+ 'packages:\n - apps/*\n - apps/remotes/*\n - services/*\n - packages/*',
145
+ "allowBuilds:\n '@swc/core': true\n core-js: true\n esbuild: true\n msgpackr-extract: true",
146
+ "onlyBuiltDependencies:\n - '@swc/core'\n - core-js\n - esbuild\n - msgpackr-extract",
147
+ ]) {
148
+ assert(
149
+ pnpmWorkspace.includes(requiredSnippet),
150
+ `pnpm-workspace.yaml must retain ${requiredSnippet.split('\n')[0]}`,
151
+ );
152
+ }
141
153
  assert(
142
154
  rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json',
143
155
  'Root must point to the UltraModern package source metadata',
@@ -330,6 +342,14 @@ for (const routePath of [
330
342
  assert(route.includes('rel="alternate"'), `${routePath} must emit alternate locale metadata`);
331
343
  assert(route.includes('hrefLang="x-default"'), `${routePath} must emit x-default metadata`);
332
344
  assert(route.includes('localizedPath('), `${routePath} must build localized URLs`);
345
+ assert(
346
+ route.includes('@modern-js/plugin-tanstack/runtime'),
347
+ `${routePath} must import TanStack runtime from @modern-js/plugin-tanstack/runtime`,
348
+ );
349
+ assert(
350
+ !route.includes(deprecatedTanstackRuntime),
351
+ `${routePath} must not import deprecated TanStack runtime path`,
352
+ );
333
353
  }
334
354
 
335
355
  const shellMf = readText('apps/shell-super-app/module-federation.config.ts');