@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 +60 -4
- package/package.json +3 -3
- package/template/.github/workflows/ultramodern-gates.yml.handlebars +1 -1
- package/template/package.json.handlebars +20 -2
- package/template/rstest.config.mts +8 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +149 -2
- package/template/src/routes/[lang]/page.tsx.handlebars +1 -1
- package/template/src/routes/layout.tsx.handlebars +1 -1
- package/template/tests/rstest.setup.ts +4 -0
- package/template/tests/tsconfig.json +7 -0
- package/template/tests/ultramodern.contract.test.ts +67 -0
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +20 -0
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
57
|
+
"frameworkVersion": "3.2.0-ultramodern.14"
|
|
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
|
-
|
|
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
|
}
|
|
@@ -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 '
|
|
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';
|
|
@@ -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');
|