@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 +64 -5
- package/package.json +3 -3
- package/template/package.json.handlebars +12 -2
- package/template/pnpm-workspace.yaml +24 -0
- package/template/rstest.config.mts +8 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +185 -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/pnpm-workspace.yaml +19 -6
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +38 -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()}
|
|
@@ -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
|
-
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
-
|
|
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
|
|
@@ -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 '
|
|
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
|
+
});
|
|
@@ -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');
|