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

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()}
@@ -1919,6 +1919,7 @@ function createTemplateManifest(modernVersion, packageSource) {
1919
1919
  ],
1920
1920
  postMaterializationValidation: [
1921
1921
  'ultramodern-workspace-contract-check',
1922
+ 'pnpm-11-policy-enforced',
1922
1923
  'template-manifest-retained'
1923
1924
  ],
1924
1925
  expectedCommands: [
@@ -2028,7 +2029,6 @@ const templateIdPattern = /^[a-z0-9][a-z0-9._-]*$/;
2028
2029
  const packageNamePattern = /^(?:@[a-z0-9._-]+\/)?[a-z0-9._-]+$/;
2029
2030
  const requiredDeniedPaths = [
2030
2031
  '.git/**',
2031
- '.github/**',
2032
2032
  '.npmrc',
2033
2033
  '.yarnrc',
2034
2034
  '.env',
@@ -2211,6 +2211,7 @@ function createBuiltinTemplateManifest(version) {
2211
2211
  allowedPaths: [
2212
2212
  '.agents/**',
2213
2213
  '.browserslistrc',
2214
+ '.github/**',
2214
2215
  '.gitignore',
2215
2216
  '.modernjs/**',
2216
2217
  '.nvmrc',
@@ -2222,11 +2223,14 @@ function createBuiltinTemplateManifest(version) {
2222
2223
  'oxfmt.config.ts',
2223
2224
  'oxlint.config.ts',
2224
2225
  'package.json',
2226
+ 'pnpm-workspace.yaml',
2225
2227
  'postcss.config.mjs',
2228
+ 'rstest.config.mts',
2226
2229
  "scripts/**",
2227
2230
  'shared/**',
2228
2231
  'src/**',
2229
2232
  'tailwind.config.ts',
2233
+ 'tests/**',
2230
2234
  'tsconfig.json'
2231
2235
  ],
2232
2236
  deniedPaths: requiredDeniedPaths,
@@ -2255,10 +2259,14 @@ function createBuiltinTemplateManifest(version) {
2255
2259
  postMaterializationValidation: [
2256
2260
  'ultramodern-contract-check',
2257
2261
  'dependency-install-with-lifecycle-deny',
2262
+ 'package-source-retained',
2263
+ 'pnpm-11-policy-enforced',
2264
+ 'rstest-smoke-tests',
2258
2265
  'template-manifest-retained'
2259
2266
  ],
2260
2267
  expectedCommands: [
2261
- "pnpm install --ignore-scripts",
2268
+ 'pnpm install',
2269
+ 'pnpm test',
2262
2270
  'pnpm run ultramodern:check'
2263
2271
  ]
2264
2272
  }
@@ -2487,6 +2495,45 @@ function singleAppModernPackageSpecifier(packageName, packageSource, useWorkspac
2487
2495
  if ('install' !== packageSource.strategy || !packageSource.aliasScope) return packageSource.modernPackageVersion;
2488
2496
  return `npm:${src_modernAliasPackageName(packageName, packageSource)}@${packageSource.modernPackageVersion}`;
2489
2497
  }
2498
+ const singleAppModernPackages = [
2499
+ '@modern-js/runtime',
2500
+ '@modern-js/app-tools',
2501
+ '@modern-js/tsconfig',
2502
+ '@modern-js/plugin-i18n',
2503
+ '@modern-js/plugin-tanstack',
2504
+ '@modern-js/plugin-bff',
2505
+ '@modern-js/adapter-rstest'
2506
+ ];
2507
+ function createSingleAppPackageSourceEvidence(packageSource, useWorkspaceProtocol) {
2508
+ const strategy = useWorkspaceProtocol ? 'workspace' : 'install';
2509
+ const specifier = useWorkspaceProtocol ? 'workspace:*' : packageSource.modernPackageVersion;
2510
+ const aliases = 'install' === strategy && packageSource.aliasScope ? Object.fromEntries(singleAppModernPackages.map((packageName)=>[
2511
+ packageName,
2512
+ src_modernAliasPackageName(packageName, packageSource)
2513
+ ])) : void 0;
2514
+ return {
2515
+ schemaVersion: 1,
2516
+ preset: 'presetUltramodern',
2517
+ strategy,
2518
+ modernPackages: {
2519
+ specifier,
2520
+ packages: singleAppModernPackages,
2521
+ ...packageSource.registry ? {
2522
+ registry: packageSource.registry
2523
+ } : {},
2524
+ ...aliases ? {
2525
+ aliases
2526
+ } : {}
2527
+ }
2528
+ };
2529
+ }
2530
+ function writeSingleAppPackageSourceEvidence(targetDir, packageSource, useWorkspaceProtocol) {
2531
+ const evidencePath = node_path.join(targetDir, '.modernjs', 'ultramodern-package-source.json');
2532
+ node_fs.mkdirSync(node_path.dirname(evidencePath), {
2533
+ recursive: true
2534
+ });
2535
+ node_fs.writeFileSync(evidencePath, `${JSON.stringify(createSingleAppPackageSourceEvidence(packageSource, useWorkspaceProtocol), null, 2)}\n`);
2536
+ }
2490
2537
  function isDirectoryEmpty(dirPath) {
2491
2538
  if (!node_fs.existsSync(dirPath)) return false;
2492
2539
  try {
@@ -2609,6 +2656,7 @@ async function main() {
2609
2656
  version: useWorkspaceProtocol ? 'workspace:*' : packageSource.modernPackageVersion,
2610
2657
  runtimeVersion: singleAppModernPackageSpecifier('@modern-js/runtime', packageSource, useWorkspaceProtocol),
2611
2658
  appToolsVersion: singleAppModernPackageSpecifier('@modern-js/app-tools', packageSource, useWorkspaceProtocol),
2659
+ adapterRstestVersion: singleAppModernPackageSpecifier('@modern-js/adapter-rstest', packageSource, useWorkspaceProtocol),
2612
2660
  tsconfigVersion: singleAppModernPackageSpecifier('@modern-js/tsconfig', packageSource, useWorkspaceProtocol),
2613
2661
  pluginTanstackVersion: singleAppModernPackageSpecifier('@modern-js/plugin-tanstack', packageSource, useWorkspaceProtocol),
2614
2662
  pluginBffVersion: singleAppModernPackageSpecifier('@modern-js/plugin-bff', packageSource, useWorkspaceProtocol),
@@ -2622,6 +2670,14 @@ async function main() {
2622
2670
  const targetPackageJson = node_path.join(targetDir, 'package.json');
2623
2671
  const packageJson = JSON.parse(node_fs.readFileSync(targetPackageJson, 'utf-8'));
2624
2672
  packageJson.name = generatedPackageName;
2673
+ packageJson.modernjs = {
2674
+ ...packageJson.modernjs ?? {},
2675
+ preset: 'presetUltramodern',
2676
+ packageSource: {
2677
+ strategy: useWorkspaceProtocol ? 'workspace' : 'install',
2678
+ config: './.modernjs/ultramodern-package-source.json'
2679
+ }
2680
+ };
2625
2681
  if (isSubproject) {
2626
2682
  delete packageJson['lint-staged'];
2627
2683
  delete packageJson['simple-git-hooks'];
@@ -2644,6 +2700,7 @@ async function main() {
2644
2700
  }
2645
2701
  node_fs.writeFileSync(targetPackageJson, `${JSON.stringify(packageJson, null, 2)}\n`);
2646
2702
  writeTemplateManifestEvidence(targetDir, templateManifest);
2703
+ writeSingleAppPackageSourceEvidence(targetDir, packageSource, useWorkspaceProtocol);
2647
2704
  const dim = '\x1b[2m\x1b[3m';
2648
2705
  const reset = '\x1b[0m';
2649
2706
  console.log(`${i18n.t(localeKeys.message.success)}\n`);
@@ -2660,6 +2717,7 @@ function copyTemplate(src, dest, options) {
2660
2717
  });
2661
2718
  const excludeInSubproject = [
2662
2719
  '.agents',
2720
+ '.github',
2663
2721
  '.gitignore.handlebars',
2664
2722
  'AGENTS.md',
2665
2723
  '.npmrc',
@@ -2689,6 +2747,7 @@ function copyTemplate(src, dest, options) {
2689
2747
  version: options.version,
2690
2748
  runtimeVersion: options.runtimeVersion,
2691
2749
  appToolsVersion: options.appToolsVersion,
2750
+ adapterRstestVersion: options.adapterRstestVersion,
2692
2751
  tsconfigVersion: options.tsconfigVersion,
2693
2752
  pluginTanstackVersion: options.pluginTanstackVersion,
2694
2753
  pluginBffVersion: options.pluginBffVersion,
@@ -2700,7 +2759,7 @@ function copyTemplate(src, dest, options) {
2700
2759
  useHonoBff: 'hono' === options.bffRuntime,
2701
2760
  bffRuntime: options.bffRuntime,
2702
2761
  enableTailwind: options.enableTailwind,
2703
- routerImportPath: 'tanstack' === options.routerFramework ? 'tanstack-router' : 'router'
2762
+ routerRuntimeImport: 'tanstack' === options.routerFramework ? '@modern-js/plugin-tanstack/runtime' : '@modern-js/runtime/router'
2704
2763
  });
2705
2764
  if (0 === rendered.trim().length) continue;
2706
2765
  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.15",
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.15"
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.15"
58
58
  }
59
59
  }
@@ -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,7 @@
62
71
  ]
63
72
  }{{/unless}},
64
73
  "engines": {
65
- "node": ">=20"
74
+ "node": ">=20",
75
+ "pnpm": ">=11.0.0"
66
76
  }
67
77
  }
@@ -0,0 +1,24 @@
1
+ minimumReleaseAge: 1440
2
+ minimumReleaseAgeStrict: true
3
+ minimumReleaseAgeIgnoreMissingTime: false
4
+ minimumReleaseAgeExclude:
5
+ - '@modern-js/*'
6
+ - '@bleedingdev/*'
7
+ - '@effect/tsgo'
8
+ - '@effect/tsgo-*'
9
+ - '@typescript/native-preview'
10
+ - '@typescript/native-preview-*'
11
+ trustPolicy: no-downgrade
12
+ trustPolicyIgnoreAfter: 1440
13
+ blockExoticSubdeps: true
14
+ engineStrict: true
15
+ pmOnFail: error
16
+ verifyDepsBeforeRun: error
17
+ strictDepBuilds: true
18
+
19
+ allowBuilds:
20
+ '@swc/core': true
21
+ core-js: true
22
+ esbuild: true
23
+ msgpackr-extract: true
24
+ simple-git-hooks: true
@@ -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,30 @@ const requiredDeniedPaths = [
48
52
  const requiredPostMaterialization = [
49
53
  'ultramodern-contract-check',
50
54
  'dependency-install-with-lifecycle-deny',
55
+ 'package-source-retained',
56
+ 'pnpm-11-policy-enforced',
57
+ 'rstest-smoke-tests',
51
58
  'template-manifest-retained',
52
59
  ];
53
60
  const requiredPaths = [
61
+ {{#unless isSubproject}}
54
62
  'AGENTS.md',
55
63
  '.agents/skills-lock.json',
64
+ '.github/workflows/ultramodern-gates.yml',
56
65
  'oxlint.config.ts',
57
66
  'oxfmt.config.ts',
58
67
  'scripts/bootstrap-agent-skills.mjs',
68
+ {{/unless}}
69
+ '.modernjs/ultramodern-package-source.json',
70
+ 'pnpm-workspace.yaml',
71
+ 'rstest.config.mts',
59
72
  'scripts/check-i18n-strings.mjs',
60
73
  'config/public/locales/en/translation.json',
61
74
  'config/public/locales/cs/translation.json',
62
75
  'src/modern-app-env.d.ts',
63
76
  'src/routes/[lang]/page.tsx',
77
+ 'tests/rstest.setup.ts',
78
+ 'tests/ultramodern.contract.test.ts',
64
79
  ];
65
80
  const manifestErrors = [];
66
81
 
@@ -86,12 +101,22 @@ for (const token of [
86
101
  'hrefLang="x-default"',
87
102
  'localizedPath(',
88
103
  '<a',
104
+ {{#if isTanstackRouter}}
105
+ "@modern-js/plugin-tanstack/runtime",
106
+ {{/if}}
89
107
  ]) {
90
108
  if (!pageContent.includes(token)) {
91
109
  console.error(`Localized route is missing ${token}`);
92
110
  process.exit(1);
93
111
  }
94
112
  }
113
+ {{#if isTanstackRouter}}
114
+ const deprecatedTanstackRuntime = '@modern-js/runtime/' + 'tanstack-router';
115
+ if (pageContent.includes(deprecatedTanstackRuntime)) {
116
+ console.error('Localized route must import TanStack runtime from @modern-js/plugin-tanstack/runtime');
117
+ process.exit(1);
118
+ }
119
+ {{/if}}
95
120
 
96
121
  if (templateManifest.schemaVersion !== 1) {
97
122
  manifestErrors.push('schemaVersion');
@@ -139,22 +164,36 @@ if (manifestErrors.length > 0) {
139
164
  const packageJson = JSON.parse(
140
165
  fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),
141
166
  );
167
+ const pnpmWorkspace = fs.readFileSync(
168
+ path.resolve(process.cwd(), 'pnpm-workspace.yaml'),
169
+ 'utf-8',
170
+ );
171
+ const packageSource = JSON.parse(fs.readFileSync(packageSourcePath, 'utf-8'));
142
172
  const unresolvedTemplateMarker = String.fromCodePoint(123, 123);
143
173
  if (JSON.stringify(packageJson).includes(unresolvedTemplateMarker)) {
144
174
  console.error('package.json contains unresolved template markers');
145
175
  process.exit(1);
146
176
  }
177
+ if (JSON.stringify(packageSource).includes(unresolvedTemplateMarker)) {
178
+ console.error('package source metadata contains unresolved template markers');
179
+ process.exit(1);
180
+ }
181
+ {{#unless isSubproject}}
147
182
  const skillsLock = JSON.parse(
148
183
  fs.readFileSync(path.resolve(process.cwd(), '.agents/skills-lock.json'), 'utf-8'),
149
184
  );
185
+ {{/unless}}
150
186
  const requiredScripts = {
187
+ 'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
188
+ test: 'rstest run',
189
+ {{#unless isSubproject}}
151
190
  format: 'oxfmt .',
152
191
  'format:check': 'oxfmt --check .',
153
- 'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
154
192
  lint: 'oxlint .',
155
193
  'lint:fix': 'oxlint . --fix',
156
194
  'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
157
195
  'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
196
+ {{/unless}}
158
197
  };
159
198
 
160
199
  for (const [scriptName, scriptCommand] of Object.entries(requiredScripts)) {
@@ -172,6 +211,142 @@ if (
172
211
  process.exit(1);
173
212
  }
174
213
 
214
+ if (!packageJson.scripts?.['ultramodern:check']?.includes('pnpm test')) {
215
+ console.error('ultramodern:check must run the generated Rstest suite');
216
+ process.exit(1);
217
+ }
218
+
219
+ if (packageJson.private !== true) {
220
+ console.error('Generated app package must be private by default');
221
+ process.exit(1);
222
+ }
223
+
224
+ if (packageJson.packageManager !== 'pnpm@11.1.2') {
225
+ console.error('Generated app package must pin pnpm@11.1.2');
226
+ process.exit(1);
227
+ }
228
+
229
+ if (packageJson.engines?.pnpm !== '>=11.0.0') {
230
+ console.error('Generated app package must require pnpm >=11.0.0');
231
+ process.exit(1);
232
+ }
233
+
234
+ if (packageJson.pnpm !== undefined) {
235
+ console.error('Generated app must keep pnpm policy in pnpm-workspace.yaml, not package.json');
236
+ process.exit(1);
237
+ }
238
+
239
+ for (const requiredSnippet of [
240
+ 'minimumReleaseAge: 1440',
241
+ 'minimumReleaseAgeStrict: true',
242
+ 'minimumReleaseAgeIgnoreMissingTime: false',
243
+ "minimumReleaseAgeExclude:\n - '@modern-js/*'\n - '@bleedingdev/*'\n - '@effect/tsgo'\n - '@effect/tsgo-*'\n - '@typescript/native-preview'\n - '@typescript/native-preview-*'",
244
+ 'trustPolicy: no-downgrade',
245
+ 'trustPolicyIgnoreAfter: 1440',
246
+ 'blockExoticSubdeps: true',
247
+ 'engineStrict: true',
248
+ 'pmOnFail: error',
249
+ 'verifyDepsBeforeRun: error',
250
+ 'strictDepBuilds: true',
251
+ "allowBuilds:\n '@swc/core': true\n core-js: true\n esbuild: true\n msgpackr-extract: true\n simple-git-hooks: true",
252
+ ]) {
253
+ if (!pnpmWorkspace.includes(requiredSnippet)) {
254
+ console.error(`pnpm-workspace.yaml must retain ${requiredSnippet.split('\n')[0]}`);
255
+ process.exit(1);
256
+ }
257
+ }
258
+
259
+ if (pnpmWorkspace.includes('onlyBuiltDependencies')) {
260
+ console.error('pnpm-workspace.yaml must use pnpm 11 allowBuilds instead of onlyBuiltDependencies');
261
+ process.exit(1);
262
+ }
263
+
264
+ if (packageJson.modernjs?.preset !== 'presetUltramodern') {
265
+ console.error('package.json must declare presetUltramodern metadata');
266
+ process.exit(1);
267
+ }
268
+
269
+ if (
270
+ packageJson.modernjs?.packageSource?.config !== './.modernjs/ultramodern-package-source.json'
271
+ ) {
272
+ console.error('package.json must retain package source metadata location');
273
+ process.exit(1);
274
+ }
275
+
276
+ if (packageSource.schemaVersion !== 1) {
277
+ console.error('Package source metadata must use schemaVersion 1');
278
+ process.exit(1);
279
+ }
280
+
281
+ if (packageSource.preset !== 'presetUltramodern') {
282
+ console.error('Package source metadata must declare presetUltramodern');
283
+ process.exit(1);
284
+ }
285
+
286
+ if (packageSource.strategy !== 'workspace' && packageSource.strategy !== 'install') {
287
+ console.error('Package source strategy must be workspace or install');
288
+ process.exit(1);
289
+ }
290
+
291
+ const expectedModernPackages = [
292
+ '@modern-js/runtime',
293
+ '@modern-js/app-tools',
294
+ '@modern-js/tsconfig',
295
+ '@modern-js/plugin-i18n',
296
+ '@modern-js/plugin-tanstack',
297
+ '@modern-js/plugin-bff',
298
+ '@modern-js/adapter-rstest',
299
+ ];
300
+
301
+ for (const packageName of expectedModernPackages) {
302
+ if (!packageSource.modernPackages?.packages?.includes(packageName)) {
303
+ console.error(`Package source metadata must include ${packageName}`);
304
+ process.exit(1);
305
+ }
306
+ }
307
+
308
+ const expectedModernSpecifier = packageSource.modernPackages?.specifier;
309
+ if (typeof expectedModernSpecifier !== 'string' || expectedModernSpecifier.length === 0) {
310
+ console.error('Package source metadata must provide a Modern package specifier');
311
+ process.exit(1);
312
+ }
313
+
314
+ const expectedModernDependency = (packageName) => {
315
+ const alias = packageSource.modernPackages?.aliases?.[packageName];
316
+ return typeof alias === 'string'
317
+ ? `npm:${alias}@${expectedModernSpecifier}`
318
+ : expectedModernSpecifier;
319
+ };
320
+
321
+ for (const packageName of [
322
+ '@modern-js/runtime',
323
+ '@modern-js/plugin-i18n',
324
+ '@modern-js/plugin-tanstack',
325
+ ]) {
326
+ if (
327
+ packageJson.dependencies?.[packageName] &&
328
+ packageJson.dependencies[packageName] !== expectedModernDependency(packageName)
329
+ ) {
330
+ console.error(`Dependency ${packageName} must match package source metadata`);
331
+ process.exit(1);
332
+ }
333
+ }
334
+
335
+ for (const packageName of [
336
+ '@modern-js/app-tools',
337
+ '@modern-js/adapter-rstest',
338
+ '@modern-js/tsconfig',
339
+ '@modern-js/plugin-bff',
340
+ ]) {
341
+ if (
342
+ packageJson.devDependencies?.[packageName] &&
343
+ packageJson.devDependencies[packageName] !== expectedModernDependency(packageName)
344
+ ) {
345
+ console.error(`Dev dependency ${packageName} must match package source metadata`);
346
+ process.exit(1);
347
+ }
348
+ }
349
+
175
350
  for (const dependency of ['@modern-js/plugin-i18n', 'i18next', 'react-i18next']) {
176
351
  if (!packageJson.dependencies?.[dependency]) {
177
352
  console.error(`Missing dependency: ${dependency}`);
@@ -181,10 +356,16 @@ for (const dependency of ['@modern-js/plugin-i18n', 'i18next', 'react-i18next'])
181
356
 
182
357
  for (const dependency of [
183
358
  '@effect/tsgo',
359
+ '@modern-js/adapter-rstest',
360
+ '@rstest/core',
361
+ '@testing-library/jest-dom',
184
362
  '@typescript/native-preview',
363
+ 'happy-dom',
364
+ {{#unless isSubproject}}
185
365
  'oxlint',
186
366
  'oxfmt',
187
367
  'ultracite',
368
+ {{/unless}}
188
369
  ]) {
189
370
  if (!packageJson.devDependencies?.[dependency]) {
190
371
  console.error(`Missing devDependency: ${dependency}`);
@@ -192,6 +373,7 @@ for (const dependency of [
192
373
  }
193
374
  }
194
375
 
376
+ {{#unless isSubproject}}
195
377
  const privateSource = skillsLock.sources?.find(
196
378
  (source) => source.repository === 'https://github.com/TechsioCZ/skills',
197
379
  );
@@ -202,5 +384,6 @@ for (const skillName of ['plan-graph', 'dag', 'subagent-graph', 'helm', 'debugge
202
384
  process.exit(1);
203
385
  }
204
386
  }
387
+ {{/unless}}
205
388
 
206
389
  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
+ });
@@ -4,14 +4,27 @@ packages:
4
4
  - services/*
5
5
  - packages/*
6
6
 
7
+ minimumReleaseAge: 1440
8
+ minimumReleaseAgeStrict: true
9
+ minimumReleaseAgeIgnoreMissingTime: false
10
+ minimumReleaseAgeExclude:
11
+ - '@modern-js/*'
12
+ - '@bleedingdev/*'
13
+ - '@effect/tsgo'
14
+ - '@effect/tsgo-*'
15
+ - '@typescript/native-preview'
16
+ - '@typescript/native-preview-*'
17
+ trustPolicy: no-downgrade
18
+ trustPolicyIgnoreAfter: 1440
19
+ blockExoticSubdeps: true
20
+ engineStrict: true
21
+ pmOnFail: error
22
+ verifyDepsBeforeRun: error
23
+ strictDepBuilds: true
24
+
7
25
  allowBuilds:
8
26
  '@swc/core': true
9
27
  core-js: true
10
28
  esbuild: true
11
29
  msgpackr-extract: true
12
-
13
- onlyBuiltDependencies:
14
- - '@swc/core'
15
- - core-js
16
- - esbuild
17
- - msgpackr-extract
30
+ simple-git-hooks: true
@@ -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,30 @@ 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
+ 'minimumReleaseAge: 1440',
146
+ 'minimumReleaseAgeStrict: true',
147
+ 'minimumReleaseAgeIgnoreMissingTime: false',
148
+ "minimumReleaseAgeExclude:\n - '@modern-js/*'\n - '@bleedingdev/*'\n - '@effect/tsgo'\n - '@effect/tsgo-*'\n - '@typescript/native-preview'\n - '@typescript/native-preview-*'",
149
+ 'trustPolicy: no-downgrade',
150
+ 'trustPolicyIgnoreAfter: 1440',
151
+ 'blockExoticSubdeps: true',
152
+ 'engineStrict: true',
153
+ 'pmOnFail: error',
154
+ 'verifyDepsBeforeRun: error',
155
+ 'strictDepBuilds: true',
156
+ "allowBuilds:\n '@swc/core': true\n core-js: true\n esbuild: true\n msgpackr-extract: true\n simple-git-hooks: true",
157
+ ]) {
158
+ assert(
159
+ pnpmWorkspace.includes(requiredSnippet),
160
+ `pnpm-workspace.yaml must retain ${requiredSnippet.split('\n')[0]}`,
161
+ );
162
+ }
163
+ assert(
164
+ !pnpmWorkspace.includes('onlyBuiltDependencies'),
165
+ 'pnpm-workspace.yaml must use pnpm 11 allowBuilds instead of onlyBuiltDependencies',
166
+ );
141
167
  assert(
142
168
  rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json',
143
169
  'Root must point to the UltraModern package source metadata',
@@ -330,6 +356,14 @@ for (const routePath of [
330
356
  assert(route.includes('rel="alternate"'), `${routePath} must emit alternate locale metadata`);
331
357
  assert(route.includes('hrefLang="x-default"'), `${routePath} must emit x-default metadata`);
332
358
  assert(route.includes('localizedPath('), `${routePath} must build localized URLs`);
359
+ assert(
360
+ route.includes('@modern-js/plugin-tanstack/runtime'),
361
+ `${routePath} must import TanStack runtime from @modern-js/plugin-tanstack/runtime`,
362
+ );
363
+ assert(
364
+ !route.includes(deprecatedTanstackRuntime),
365
+ `${routePath} must not import deprecated TanStack runtime path`,
366
+ );
333
367
  }
334
368
 
335
369
  const shellMf = readText('apps/shell-super-app/module-federation.config.ts');
@@ -468,5 +502,9 @@ assert(
468
502
  manifest.validation?.expectedCommands?.includes('pnpm run ultramodern:check'),
469
503
  'Template manifest must document the validation command',
470
504
  );
505
+ assert(
506
+ manifest.validation?.postMaterializationValidation?.includes('pnpm-11-policy-enforced'),
507
+ 'Template manifest must document pnpm 11 policy validation',
508
+ );
471
509
 
472
510
  console.log('UltraModern workspace scaffold validated');