@bleedingdev/modern-js-create 3.2.0-ultramodern.21 → 3.2.0-ultramodern.22
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/README.md +20 -14
- package/dist/index.js +1460 -321
- package/dist/types/locale/en.d.ts +2 -0
- package/dist/types/locale/zh.d.ts +2 -0
- package/dist/types/ultramodern-workspace.d.ts +13 -0
- package/package.json +6 -6
- package/template/.mise.toml +2 -0
- package/template/README.md +7 -6
- package/template/package.json.handlebars +16 -16
- package/template/pnpm-workspace.yaml +2 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +138 -26
- package/template/tests/{ultramodern.contract.test.ts → ultramodern.contract.test.ts.handlebars} +28 -0
- package/template-workspace/.agents/skills-lock.json +19 -0
- package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +1 -1
- package/template-workspace/.mise.toml +2 -0
- package/template-workspace/AGENTS.md +7 -7
- package/template-workspace/README.md.handlebars +12 -3
- package/template-workspace/pnpm-workspace.yaml +15 -0
- package/template-workspace/scripts/assert-mf-types.mjs +44 -0
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +73 -13
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +449 -121
- package/template-workspace/scripts/check-i18n-strings.mjs +0 -83
|
@@ -35,6 +35,7 @@ export declare const EN_LOCALE: {
|
|
|
35
35
|
optionUltramodernPackageSource: string;
|
|
36
36
|
optionUltramodernPackageScope: string;
|
|
37
37
|
optionUltramodernPackageNamePrefix: string;
|
|
38
|
+
optionMicroVertical: string;
|
|
38
39
|
optionSub: string;
|
|
39
40
|
examples: string;
|
|
40
41
|
example1: string;
|
|
@@ -47,6 +48,7 @@ export declare const EN_LOCALE: {
|
|
|
47
48
|
example8: string;
|
|
48
49
|
example9: string;
|
|
49
50
|
example10: string;
|
|
51
|
+
example11: string;
|
|
50
52
|
moreInfo: string;
|
|
51
53
|
};
|
|
52
54
|
version: {
|
|
@@ -35,6 +35,7 @@ export declare const ZH_LOCALE: {
|
|
|
35
35
|
optionUltramodernPackageSource: string;
|
|
36
36
|
optionUltramodernPackageScope: string;
|
|
37
37
|
optionUltramodernPackageNamePrefix: string;
|
|
38
|
+
optionMicroVertical: string;
|
|
38
39
|
optionSub: string;
|
|
39
40
|
examples: string;
|
|
40
41
|
example1: string;
|
|
@@ -47,6 +48,7 @@ export declare const ZH_LOCALE: {
|
|
|
47
48
|
example8: string;
|
|
48
49
|
example9: string;
|
|
49
50
|
example10: string;
|
|
51
|
+
example11: string;
|
|
50
52
|
moreInfo: string;
|
|
51
53
|
};
|
|
52
54
|
version: {
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
export type MicroVerticalKind = 'remote' | 'horizontal-remote' | 'service' | 'shared';
|
|
1
2
|
type UltramodernPackageSourceStrategy = 'workspace' | 'install';
|
|
2
3
|
export type UltramodernWorkspaceOptions = {
|
|
3
4
|
targetDir: string;
|
|
4
5
|
packageName: string;
|
|
5
6
|
modernVersion: string;
|
|
7
|
+
enableTailwind?: boolean;
|
|
6
8
|
packageSource?: {
|
|
7
9
|
strategy?: UltramodernPackageSourceStrategy;
|
|
8
10
|
modernPackageVersion?: string;
|
|
@@ -11,10 +13,21 @@ export type UltramodernWorkspaceOptions = {
|
|
|
11
13
|
aliasPackageNamePrefix?: string;
|
|
12
14
|
};
|
|
13
15
|
};
|
|
16
|
+
export type AddUltramodernMicroVerticalOptions = {
|
|
17
|
+
workspaceRoot: string;
|
|
18
|
+
name: string;
|
|
19
|
+
kind: MicroVerticalKind;
|
|
20
|
+
modernVersion: string;
|
|
21
|
+
enableTailwind?: boolean;
|
|
22
|
+
packageSource?: UltramodernWorkspaceOptions['packageSource'];
|
|
23
|
+
};
|
|
14
24
|
export declare const ULTRAMODERN_WORKSPACE_FLAG = "--ultramodern-workspace";
|
|
25
|
+
export declare function addUltramodernMicroVertical(options: AddUltramodernMicroVerticalOptions): void;
|
|
15
26
|
export declare function generateUltramodernWorkspace(options: UltramodernWorkspaceOptions): void;
|
|
16
27
|
export declare const ultramodernWorkspaceVersions: {
|
|
17
28
|
tanstackRouter: string;
|
|
18
29
|
moduleFederation: string;
|
|
30
|
+
tailwind: string;
|
|
31
|
+
tailwindPostcss: string;
|
|
19
32
|
};
|
|
20
33
|
export {};
|
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.22",
|
|
25
25
|
"types": "./dist/types/index.d.ts",
|
|
26
26
|
"main": "./dist/index.js",
|
|
27
27
|
"bin": {
|
|
@@ -38,10 +38,10 @@
|
|
|
38
38
|
],
|
|
39
39
|
"devDependencies": {
|
|
40
40
|
"@rslib/core": "0.21.5",
|
|
41
|
-
"@types/node": "^25.
|
|
42
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
43
|
-
"tsx": "^4.22.
|
|
44
|
-
"@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.
|
|
41
|
+
"@types/node": "^25.9.1",
|
|
42
|
+
"@typescript/native-preview": "7.0.0-dev.20260526.1",
|
|
43
|
+
"tsx": "^4.22.3",
|
|
44
|
+
"@modern-js/i18n-utils": "npm:@bleedingdev/modern-js-i18n-utils@3.2.0-ultramodern.22"
|
|
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.22"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/template/README.md
CHANGED
|
@@ -46,14 +46,15 @@ major upgrades.
|
|
|
46
46
|
|
|
47
47
|
## Micro Vertical Workspaces
|
|
48
48
|
|
|
49
|
-
Inside a Micro Vertical workspace,
|
|
50
|
-
|
|
49
|
+
Inside a Micro Vertical workspace, add packages from the workspace root with
|
|
50
|
+
the UltraModern add flow. It derives paths, package names, ports, Module
|
|
51
|
+
Federation names, topology entries, overlays, ownership, and root dev scripts:
|
|
51
52
|
|
|
52
53
|
```bash
|
|
53
|
-
npx @modern-js/create
|
|
54
|
-
npx @modern-js/create
|
|
55
|
-
npx @modern-js/create
|
|
56
|
-
npx @modern-js/create
|
|
54
|
+
npx @modern-js/create catalog --microvertical remote
|
|
55
|
+
npx @modern-js/create design-system --microvertical horizontal-remote
|
|
56
|
+
npx @modern-js/create catalog-api --microvertical service
|
|
57
|
+
npx @modern-js/create catalog-contracts --microvertical shared
|
|
57
58
|
```
|
|
58
59
|
|
|
59
60
|
The canonical topology is documented in
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
"version": "0.1.0",
|
|
4
4
|
"private": true,
|
|
5
5
|
"type": "module",
|
|
6
|
-
"packageManager": "pnpm@11.
|
|
6
|
+
"packageManager": "pnpm@11.3.0",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"reset": "npx rimraf node_modules ./**/node_modules",
|
|
9
9
|
"dev": "modern dev",
|
|
@@ -28,36 +28,36 @@
|
|
|
28
28
|
{{#if isTanstackRouter}} "@modern-js/plugin-tanstack": "{{pluginTanstackVersion}}",
|
|
29
29
|
{{/if}}
|
|
30
30
|
"@modern-js/runtime": "{{runtimeVersion}}",
|
|
31
|
-
{{#if isTanstackRouter}} "@tanstack/react-router": "
|
|
31
|
+
{{#if isTanstackRouter}} "@tanstack/react-router": "{{tanstackRouterVersion}}",
|
|
32
32
|
{{/if}}
|
|
33
33
|
"i18next": "26.2.0",
|
|
34
|
-
"react": "^19.2.
|
|
35
|
-
"react-dom": "^19.2.
|
|
34
|
+
"react": "^19.2.6",
|
|
35
|
+
"react-dom": "^19.2.6",
|
|
36
36
|
"react-i18next": "17.0.8"
|
|
37
37
|
},
|
|
38
38
|
"devDependencies": {
|
|
39
|
-
"@effect/tsgo": "0.
|
|
39
|
+
"@effect/tsgo": "0.11.0",
|
|
40
40
|
"@modern-js/adapter-rstest": "{{adapterRstestVersion}}",
|
|
41
41
|
"@modern-js/app-tools": "{{appToolsVersion}}",
|
|
42
42
|
{{#if enableBff}} "@modern-js/plugin-bff": "{{pluginBffVersion}}",
|
|
43
43
|
{{/if}} "@modern-js/tsconfig": "{{tsconfigVersion}}",
|
|
44
|
-
"@rstest/core": "0.10.
|
|
44
|
+
"@rstest/core": "0.10.2",
|
|
45
45
|
{{#if enableTailwind}}
|
|
46
|
-
"@tailwindcss/postcss": "^
|
|
46
|
+
"@tailwindcss/postcss": "^{{tailwindPostcssVersion}}",
|
|
47
47
|
{{/if}}
|
|
48
48
|
"@types/node": "^20",
|
|
49
49
|
"@types/react": "^19.1.8",
|
|
50
50
|
"@types/react-dom": "^19.1.6",
|
|
51
|
-
"@typescript/native-preview": "7.0.0-dev.
|
|
52
|
-
"happy-dom": "^20.
|
|
51
|
+
"@typescript/native-preview": "7.0.0-dev.20260526.1",
|
|
52
|
+
"happy-dom": "^20.9.0",
|
|
53
53
|
{{#unless isSubproject}}
|
|
54
|
-
"lint-staged": "~
|
|
55
|
-
"oxfmt": "0.
|
|
56
|
-
"oxlint": "1.
|
|
54
|
+
"lint-staged": "~17.0.5",
|
|
55
|
+
"oxfmt": "0.51.0",
|
|
56
|
+
"oxlint": "1.66.0",
|
|
57
57
|
{{/unless}}{{#if enableTailwind}} "postcss": "^8.5.6",
|
|
58
|
-
{{/if}} "rimraf": "^6.
|
|
59
|
-
"simple-git-hooks": "^2.
|
|
60
|
-
"tailwindcss": "^
|
|
58
|
+
{{/if}} "rimraf": "^6.1.3"{{#unless isSubproject}},
|
|
59
|
+
"simple-git-hooks": "^2.13.1"{{/unless}}{{#if enableTailwind}},
|
|
60
|
+
"tailwindcss": "^{{tailwindVersion}}"{{/if}}{{#unless isSubproject}},
|
|
61
61
|
"ultracite": "7.7.0"{{/unless}}
|
|
62
62
|
}{{#unless isSubproject}},
|
|
63
63
|
"simple-git-hooks": {
|
|
@@ -71,6 +71,6 @@
|
|
|
71
71
|
}{{/unless}},
|
|
72
72
|
"engines": {
|
|
73
73
|
"node": ">=20",
|
|
74
|
-
"pnpm": ">=11.0.0"
|
|
74
|
+
"pnpm": ">=11.3.0 <11.4.0"
|
|
75
75
|
}
|
|
76
76
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
@@ -7,7 +8,23 @@ const packageSourcePath = path.resolve(
|
|
|
7
8
|
process.cwd(),
|
|
8
9
|
'.modernjs/ultramodern-package-source.json',
|
|
9
10
|
);
|
|
11
|
+
const readPnpmConfig = (key) => {
|
|
12
|
+
const env = { ...process.env };
|
|
13
|
+
for (const envKey of Object.keys(env)) {
|
|
14
|
+
if (/^(?:npm|pnpm)_config_/i.test(envKey)) {
|
|
15
|
+
delete env[envKey];
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const output = execFileSync('pnpm', ['config', 'get', key, '--json'], {
|
|
19
|
+
cwd: process.cwd(),
|
|
20
|
+
env,
|
|
21
|
+
encoding: 'utf-8',
|
|
22
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
23
|
+
}).trim();
|
|
24
|
+
return output ? JSON.parse(output) : undefined;
|
|
25
|
+
};
|
|
10
26
|
const isSubproject = {{isSubproject}};
|
|
27
|
+
const enableTailwind = {{enableTailwind}};
|
|
11
28
|
|
|
12
29
|
if (!fs.existsSync(configPath)) {
|
|
13
30
|
console.error('modern.config.ts not found');
|
|
@@ -68,13 +85,19 @@ const requiredPaths = [
|
|
|
68
85
|
'oxfmt.config.ts',
|
|
69
86
|
'scripts/bootstrap-agent-skills.mjs',
|
|
70
87
|
{{/unless}}
|
|
88
|
+
'.mise.toml',
|
|
71
89
|
'.modernjs/ultramodern-package-source.json',
|
|
72
90
|
'pnpm-workspace.yaml',
|
|
73
91
|
'rstest.config.mts',
|
|
74
92
|
'scripts/check-i18n-strings.mjs',
|
|
93
|
+
{{#if enableTailwind}}
|
|
94
|
+
'postcss.config.mjs',
|
|
95
|
+
'tailwind.config.ts',
|
|
96
|
+
{{/if}}
|
|
75
97
|
'config/public/locales/en/translation.json',
|
|
76
98
|
'config/public/locales/cs/translation.json',
|
|
77
99
|
'src/modern-app-env.d.ts',
|
|
100
|
+
'src/routes/index.css',
|
|
78
101
|
'src/routes/[lang]/page.tsx',
|
|
79
102
|
'tests/ultramodern.contract.test.ts',
|
|
80
103
|
];
|
|
@@ -92,6 +115,36 @@ if (fs.existsSync(path.resolve(process.cwd(), 'src/routes/page.tsx'))) {
|
|
|
92
115
|
process.exit(1);
|
|
93
116
|
}
|
|
94
117
|
|
|
118
|
+
const routeCss = fs.readFileSync(path.resolve(process.cwd(), 'src/routes/index.css'), 'utf-8');
|
|
119
|
+
if (enableTailwind) {
|
|
120
|
+
if (!routeCss.includes("@import 'tailwindcss';")) {
|
|
121
|
+
console.error('src/routes/index.css must import Tailwind CSS by default');
|
|
122
|
+
process.exit(1);
|
|
123
|
+
}
|
|
124
|
+
const postcssConfig = fs.readFileSync(path.resolve(process.cwd(), 'postcss.config.mjs'), 'utf-8');
|
|
125
|
+
if (!postcssConfig.includes("'@tailwindcss/postcss'")) {
|
|
126
|
+
console.error('postcss.config.mjs must configure @tailwindcss/postcss');
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
const tailwindConfig = fs.readFileSync(path.resolve(process.cwd(), 'tailwind.config.ts'), 'utf-8');
|
|
130
|
+
if (!tailwindConfig.includes("content: ['./src/**/*.{js,ts,jsx,tsx}']")) {
|
|
131
|
+
console.error('tailwind.config.ts must scan generated source files');
|
|
132
|
+
process.exit(1);
|
|
133
|
+
}
|
|
134
|
+
} else {
|
|
135
|
+
if (routeCss.includes("@import 'tailwindcss';")) {
|
|
136
|
+
console.error('src/routes/index.css must omit Tailwind CSS when disabled');
|
|
137
|
+
process.exit(1);
|
|
138
|
+
}
|
|
139
|
+
if (
|
|
140
|
+
fs.existsSync(path.resolve(process.cwd(), 'postcss.config.mjs')) ||
|
|
141
|
+
fs.existsSync(path.resolve(process.cwd(), 'tailwind.config.ts'))
|
|
142
|
+
) {
|
|
143
|
+
console.error('Tailwind config files must not be written when Tailwind is disabled');
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
|
|
95
148
|
{{#unless isSubproject}}
|
|
96
149
|
const workflowContent = fs.readFileSync(
|
|
97
150
|
path.resolve(process.cwd(), '.github/workflows/ultramodern-gates.yml'),
|
|
@@ -212,10 +265,6 @@ if (manifestErrors.length > 0) {
|
|
|
212
265
|
const packageJson = JSON.parse(
|
|
213
266
|
fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),
|
|
214
267
|
);
|
|
215
|
-
const pnpmWorkspace = fs.readFileSync(
|
|
216
|
-
path.resolve(process.cwd(), 'pnpm-workspace.yaml'),
|
|
217
|
-
'utf-8',
|
|
218
|
-
);
|
|
219
268
|
const packageSource = JSON.parse(fs.readFileSync(packageSourcePath, 'utf-8'));
|
|
220
269
|
const unresolvedTemplateMarker = String.fromCodePoint(123, 123);
|
|
221
270
|
if (JSON.stringify(packageJson).includes(unresolvedTemplateMarker)) {
|
|
@@ -269,13 +318,13 @@ if (packageJson.private !== true) {
|
|
|
269
318
|
process.exit(1);
|
|
270
319
|
}
|
|
271
320
|
|
|
272
|
-
if (packageJson.packageManager !== 'pnpm@11.
|
|
273
|
-
console.error('Generated app package must pin pnpm@11.
|
|
321
|
+
if (packageJson.packageManager !== 'pnpm@11.3.0') {
|
|
322
|
+
console.error('Generated app package must pin pnpm@11.3.0');
|
|
274
323
|
process.exit(1);
|
|
275
324
|
}
|
|
276
325
|
|
|
277
|
-
if (packageJson.engines?.pnpm !== '>=11.0.0') {
|
|
278
|
-
console.error('Generated app package must require pnpm >=11.0.0');
|
|
326
|
+
if (packageJson.engines?.pnpm !== '>=11.3.0 <11.4.0') {
|
|
327
|
+
console.error('Generated app package must require pnpm >=11.3.0 <11.4.0');
|
|
279
328
|
process.exit(1);
|
|
280
329
|
}
|
|
281
330
|
|
|
@@ -284,28 +333,66 @@ if (packageJson.pnpm !== undefined) {
|
|
|
284
333
|
process.exit(1);
|
|
285
334
|
}
|
|
286
335
|
|
|
287
|
-
|
|
288
|
-
'minimumReleaseAge
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
'
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
'
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
336
|
+
if (readPnpmConfig('minimumReleaseAge') !== 1440) {
|
|
337
|
+
console.error('pnpm minimumReleaseAge must be 1440');
|
|
338
|
+
process.exit(1);
|
|
339
|
+
}
|
|
340
|
+
if (readPnpmConfig('minimumReleaseAgeStrict') !== true) {
|
|
341
|
+
console.error('pnpm minimumReleaseAgeStrict must be true');
|
|
342
|
+
process.exit(1);
|
|
343
|
+
}
|
|
344
|
+
if (readPnpmConfig('minimumReleaseAgeIgnoreMissingTime') !== false) {
|
|
345
|
+
console.error('pnpm minimumReleaseAgeIgnoreMissingTime must be false');
|
|
346
|
+
process.exit(1);
|
|
347
|
+
}
|
|
348
|
+
if (
|
|
349
|
+
JSON.stringify(readPnpmConfig('minimumReleaseAgeExclude')) !==
|
|
350
|
+
JSON.stringify([
|
|
351
|
+
'@modern-js/*',
|
|
352
|
+
'@bleedingdev/*',
|
|
353
|
+
'@effect/tsgo',
|
|
354
|
+
'@effect/tsgo-*',
|
|
355
|
+
'@typescript/native-preview',
|
|
356
|
+
'@typescript/native-preview-*',
|
|
357
|
+
])
|
|
358
|
+
) {
|
|
359
|
+
console.error('pnpm minimumReleaseAgeExclude must retain framework exceptions');
|
|
360
|
+
process.exit(1);
|
|
361
|
+
}
|
|
362
|
+
if (readPnpmConfig('trustPolicy') !== 'no-downgrade') {
|
|
363
|
+
console.error('pnpm trustPolicy must be no-downgrade');
|
|
364
|
+
process.exit(1);
|
|
365
|
+
}
|
|
366
|
+
for (const [key, expected] of [
|
|
367
|
+
['trustPolicyIgnoreAfter', 1440],
|
|
368
|
+
['blockExoticSubdeps', true],
|
|
369
|
+
['engineStrict', true],
|
|
370
|
+
['pmOnFail', 'error'],
|
|
371
|
+
['verifyDepsBeforeRun', 'error'],
|
|
372
|
+
['strictDepBuilds', true],
|
|
300
373
|
]) {
|
|
301
|
-
if (
|
|
302
|
-
console.error(`pnpm
|
|
374
|
+
if (readPnpmConfig(key) !== expected) {
|
|
375
|
+
console.error(`pnpm ${key} must be ${String(expected)}`);
|
|
303
376
|
process.exit(1);
|
|
304
377
|
}
|
|
305
378
|
}
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
379
|
+
if (
|
|
380
|
+
JSON.stringify(readPnpmConfig('allowBuilds')) !==
|
|
381
|
+
JSON.stringify({
|
|
382
|
+
'@swc/core': true,
|
|
383
|
+
'core-js': true,
|
|
384
|
+
esbuild: true,
|
|
385
|
+
'msgpackr-extract': true,
|
|
386
|
+
sharp: true,
|
|
387
|
+
'simple-git-hooks': true,
|
|
388
|
+
workerd: true,
|
|
389
|
+
})
|
|
390
|
+
) {
|
|
391
|
+
console.error('pnpm allowBuilds must approve only the generated app build dependencies');
|
|
392
|
+
process.exit(1);
|
|
393
|
+
}
|
|
394
|
+
if (readPnpmConfig('onlyBuiltDependencies') !== undefined) {
|
|
395
|
+
console.error('pnpm onlyBuiltDependencies must not be set');
|
|
309
396
|
process.exit(1);
|
|
310
397
|
}
|
|
311
398
|
|
|
@@ -408,6 +495,11 @@ for (const dependency of [
|
|
|
408
495
|
'@rstest/core',
|
|
409
496
|
'@typescript/native-preview',
|
|
410
497
|
'happy-dom',
|
|
498
|
+
{{#if enableTailwind}}
|
|
499
|
+
'@tailwindcss/postcss',
|
|
500
|
+
'postcss',
|
|
501
|
+
'tailwindcss',
|
|
502
|
+
{{/if}}
|
|
411
503
|
{{#unless isSubproject}}
|
|
412
504
|
'oxlint',
|
|
413
505
|
'oxfmt',
|
|
@@ -420,6 +512,26 @@ for (const dependency of [
|
|
|
420
512
|
}
|
|
421
513
|
}
|
|
422
514
|
|
|
515
|
+
{{#if enableTailwind}}
|
|
516
|
+
if (
|
|
517
|
+
packageJson.devDependencies?.tailwindcss !== '^4.3.0' ||
|
|
518
|
+
packageJson.devDependencies?.['@tailwindcss/postcss'] !== '^4.3.0'
|
|
519
|
+
) {
|
|
520
|
+
console.error('Tailwind CSS dependencies must use the UltraModern default baseline');
|
|
521
|
+
process.exit(1);
|
|
522
|
+
}
|
|
523
|
+
{{/if}}
|
|
524
|
+
{{#unless enableTailwind}}
|
|
525
|
+
if (
|
|
526
|
+
packageJson.devDependencies?.tailwindcss ||
|
|
527
|
+
packageJson.devDependencies?.['@tailwindcss/postcss'] ||
|
|
528
|
+
packageJson.devDependencies?.postcss
|
|
529
|
+
) {
|
|
530
|
+
console.error('Tailwind CSS dependencies must be absent when Tailwind is disabled');
|
|
531
|
+
process.exit(1);
|
|
532
|
+
}
|
|
533
|
+
{{/unless}}
|
|
534
|
+
|
|
423
535
|
{{#unless isSubproject}}
|
|
424
536
|
const privateSource = skillsLock.sources?.find(
|
|
425
537
|
(source) => source.repository === 'https://github.com/TechsioCZ/skills',
|
package/template/tests/{ultramodern.contract.test.ts → ultramodern.contract.test.ts.handlebars}
RENAMED
|
@@ -20,6 +20,22 @@ describe('generated UltraModern contract', () => {
|
|
|
20
20
|
expect(page).toContain('rel="alternate"');
|
|
21
21
|
expect(page).toContain('hrefLang="x-default"');
|
|
22
22
|
expect(page).toContain('localizedPath(');
|
|
23
|
+
{{#if enableTailwind}}
|
|
24
|
+
expect(readText('src/routes/index.css')).toContain(
|
|
25
|
+
"@import 'tailwindcss';",
|
|
26
|
+
);
|
|
27
|
+
expect(readText('postcss.config.mjs')).toContain('@tailwindcss/postcss');
|
|
28
|
+
expect(readText('tailwind.config.ts')).toContain(
|
|
29
|
+
"content: ['./src/**/*.{js,ts,jsx,tsx}']",
|
|
30
|
+
);
|
|
31
|
+
{{/if}}
|
|
32
|
+
{{#unless enableTailwind}}
|
|
33
|
+
expect(readText('src/routes/index.css')).not.toContain(
|
|
34
|
+
"@import 'tailwindcss';",
|
|
35
|
+
);
|
|
36
|
+
expect(fs.existsSync(path.join(root, 'postcss.config.mjs'))).toBe(false);
|
|
37
|
+
expect(fs.existsSync(path.join(root, 'tailwind.config.ts'))).toBe(false);
|
|
38
|
+
{{/unless}}
|
|
23
39
|
|
|
24
40
|
const config = readText('rstest.config.mts');
|
|
25
41
|
expect(config).toContain("withModernConfig()");
|
|
@@ -63,5 +79,17 @@ describe('generated UltraModern contract', () => {
|
|
|
63
79
|
expect(
|
|
64
80
|
packageJson.devDependencies?.['@modern-js/adapter-rstest'],
|
|
65
81
|
).toBeTruthy();
|
|
82
|
+
{{#if enableTailwind}}
|
|
83
|
+
expect(packageJson.devDependencies?.tailwindcss).toBe('^4.3.0');
|
|
84
|
+
expect(packageJson.devDependencies?.['@tailwindcss/postcss']).toBe(
|
|
85
|
+
'^4.3.0',
|
|
86
|
+
);
|
|
87
|
+
{{/if}}
|
|
88
|
+
{{#unless enableTailwind}}
|
|
89
|
+
expect(packageJson.devDependencies?.tailwindcss).toBeUndefined();
|
|
90
|
+
expect(
|
|
91
|
+
packageJson.devDependencies?.['@tailwindcss/postcss'],
|
|
92
|
+
).toBeUndefined();
|
|
93
|
+
{{/unless}}
|
|
66
94
|
});
|
|
67
95
|
});
|
|
@@ -17,6 +17,20 @@
|
|
|
17
17
|
"licensePath": ".agents/rstackjs-agent-skills-LICENSE",
|
|
18
18
|
"install": "vendored"
|
|
19
19
|
},
|
|
20
|
+
{
|
|
21
|
+
"id": "module-federation-agent-skills",
|
|
22
|
+
"visibility": "public",
|
|
23
|
+
"repository": "https://github.com/module-federation/agent-skills",
|
|
24
|
+
"commit": "07bb5b6c43ad457609e00c081b72d4c42508ec76",
|
|
25
|
+
"install": "clone",
|
|
26
|
+
"baseline": [
|
|
27
|
+
{
|
|
28
|
+
"name": "mf",
|
|
29
|
+
"path": ".agents/skills/mf",
|
|
30
|
+
"reason": "Module Federation docs, config inspection, type checking, shared dependency checks, and observability troubleshooting"
|
|
31
|
+
}
|
|
32
|
+
]
|
|
33
|
+
},
|
|
20
34
|
{
|
|
21
35
|
"id": "techsiocz-private",
|
|
22
36
|
"visibility": "private",
|
|
@@ -81,6 +95,11 @@
|
|
|
81
95
|
"name": "rstest-best-practices",
|
|
82
96
|
"path": ".agents/skills/rstest-best-practices",
|
|
83
97
|
"reason": "Rstest configuration, test writing, mocking, snapshots, coverage, and CI behavior"
|
|
98
|
+
},
|
|
99
|
+
{
|
|
100
|
+
"name": "mf",
|
|
101
|
+
"path": ".agents/skills/mf",
|
|
102
|
+
"reason": "Module Federation docs, config inspection, type checking, shared dependency checks, and observability troubleshooting"
|
|
84
103
|
}
|
|
85
104
|
],
|
|
86
105
|
"excludedByDefault": [
|
|
@@ -7,14 +7,11 @@ This workspace is generated as an agent-ready UltraModern.js SuperApp. Agents sh
|
|
|
7
7
|
- `pnpm lint` runs Oxlint with the Ultracite preset.
|
|
8
8
|
- `pnpm format` runs oxfmt.
|
|
9
9
|
- `pnpm typecheck` runs effect-tsgo as the TypeScript checker.
|
|
10
|
-
- `pnpm
|
|
11
|
-
- `pnpm check` runs formatting, linting, effect-tsgo, i18n checks, private-skill availability checks, and the generated workspace contract.
|
|
10
|
+
- `pnpm check` runs formatting, linting, effect-tsgo, private-skill availability checks, and the generated workspace contract.
|
|
12
11
|
|
|
13
|
-
##
|
|
12
|
+
## Localized Routes
|
|
14
13
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
Routes are locale-prefixed by default through `localePathRedirect: true`. Keep localized app pages under `src/routes/[lang]`, use links for language switching, and preserve canonical plus `hreflang` metadata. Production builds fail unless `MODERN_PUBLIC_SITE_URL` is set per deployed app, so canonical URLs always use the production origin.
|
|
14
|
+
Generated apps keep locale-prefixed entry routes under `src/routes/[lang]`, static language links, and canonical plus `hreflang` metadata. Runtime i18n is not enabled in the starter because the current React 19 + Module Federation streaming SSR stack must render predictably first. Production builds fail unless `MODERN_PUBLIC_SITE_URL` is set per deployed app, so canonical URLs always use the production origin.
|
|
18
15
|
|
|
19
16
|
## Required Skill Baseline
|
|
20
17
|
|
|
@@ -27,6 +24,9 @@ Use these skills when the task touches the matching subsystem:
|
|
|
27
24
|
- `rslib-best-practices`: Shared packages, generated libraries, declaration output, and Rslib configuration.
|
|
28
25
|
- `rslib-modern-package`: Package contracts for shared libraries, exports, side effects, dependency placement, README, and release readiness.
|
|
29
26
|
- `rstest-best-practices`: Rstest configuration, test writing, mocking, snapshots, coverage, and CI test behavior.
|
|
27
|
+
- `mf`: Module Federation docs, Modern.js integration, DTS/type checks, shared dependency checks, runtime errors, and observability troubleshooting.
|
|
28
|
+
|
|
29
|
+
The public `module-federation/agent-skills` repository is installed during `pnpm install` and `pnpm skills:install`. `pnpm skills:check` fails when the required public `mf` skill is missing.
|
|
30
30
|
|
|
31
31
|
## Private Skills
|
|
32
32
|
|
|
@@ -58,4 +58,4 @@ Agents may read files under `repos/` to understand upstream patterns, APIs, and
|
|
|
58
58
|
|
|
59
59
|
## Skill Provenance
|
|
60
60
|
|
|
61
|
-
The vendored Rstack skills and private TechsioCZ skill allowlist are pinned in `.agents/skills-lock.json`. Do not update, remove, or replace them casually. If a skill needs updating, update the lock file and run `pnpm check`.
|
|
61
|
+
The vendored Rstack skills, public Module Federation skill, and private TechsioCZ skill allowlist are pinned in `.agents/skills-lock.json`. Do not update, remove, or replace them casually. If a skill needs updating, update the lock file and run `pnpm check`.
|
|
@@ -7,12 +7,21 @@ UltraModern.js 3.0 SuperApp surface and scaffolds the canonical Micro Vertical
|
|
|
7
7
|
starter topology:
|
|
8
8
|
|
|
9
9
|
- `apps/shell-super-app` owns shell route assembly and remote manifest wiring.
|
|
10
|
-
- `apps/remotes/remote-commerce` owns the commerce vertical remote
|
|
11
|
-
-
|
|
10
|
+
- `apps/remotes/remote-commerce` owns the commerce vertical remote, its
|
|
11
|
+
browser-safe Module Federation exposes, and its Effect BFF/API surface.
|
|
12
|
+
- `apps/remotes/remote-identity` owns the identity vertical remote, its
|
|
13
|
+
browser-safe Module Federation exposes, and its Effect BFF/API surface.
|
|
12
14
|
- `apps/remotes/remote-design-system` owns the horizontal design-system remote.
|
|
13
|
-
- `services/service-recommendations-effect` owns the Effect BFF service.
|
|
14
15
|
- `packages/shared-*` provide placeholders for cross-workspace contracts.
|
|
15
16
|
|
|
17
|
+
Default vertical APIs are owned by the vertical package, not by `services/*`.
|
|
18
|
+
Each full-stack vertical keeps its `api/effect/index.ts` handlers next to its
|
|
19
|
+
`shared/effect/api.ts` contract and `src/effect/*-client.ts` typed client
|
|
20
|
+
surface. The Module Federation config exposes only browser-safe Route and
|
|
21
|
+
Widget modules; server handlers and Effect client/contract modules stay out of
|
|
22
|
+
browser remotes. `services/*` is reserved for explicit external service
|
|
23
|
+
packages.
|
|
24
|
+
|
|
16
25
|
Run the scaffold validator before adding business code:
|
|
17
26
|
|
|
18
27
|
```bash
|
|
@@ -14,6 +14,10 @@ minimumReleaseAgeExclude:
|
|
|
14
14
|
- '@effect/tsgo-*'
|
|
15
15
|
- '@typescript/native-preview'
|
|
16
16
|
- '@typescript/native-preview-*'
|
|
17
|
+
- '@cloudflare/*'
|
|
18
|
+
- miniflare
|
|
19
|
+
- workerd
|
|
20
|
+
- wrangler
|
|
17
21
|
trustPolicy: no-downgrade
|
|
18
22
|
trustPolicyIgnoreAfter: 1440
|
|
19
23
|
blockExoticSubdeps: true
|
|
@@ -22,9 +26,20 @@ pmOnFail: error
|
|
|
22
26
|
verifyDepsBeforeRun: error
|
|
23
27
|
strictDepBuilds: true
|
|
24
28
|
|
|
29
|
+
peerDependencyRules:
|
|
30
|
+
allowedVersions:
|
|
31
|
+
react: '>=19.0.0'
|
|
32
|
+
typescript: '>=6.0.0'
|
|
33
|
+
|
|
34
|
+
overrides:
|
|
35
|
+
'@tanstack/react-router': 1.170.8
|
|
36
|
+
node-fetch: '^3.3.2'
|
|
37
|
+
|
|
25
38
|
allowBuilds:
|
|
26
39
|
'@swc/core': true
|
|
27
40
|
core-js: true
|
|
28
41
|
esbuild: true
|
|
29
42
|
msgpackr-extract: true
|
|
43
|
+
sharp: true
|
|
30
44
|
simple-git-hooks: true
|
|
45
|
+
workerd: true
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
|
|
4
|
+
const root = process.cwd();
|
|
5
|
+
const defaultAppDirs = [
|
|
6
|
+
'apps/remotes/remote-commerce',
|
|
7
|
+
'apps/remotes/remote-identity',
|
|
8
|
+
'apps/remotes/remote-design-system',
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
const candidateDirs = process.argv.slice(2);
|
|
12
|
+
const appDirs = candidateDirs.length
|
|
13
|
+
? candidateDirs
|
|
14
|
+
: fs.existsSync(path.join(root, 'module-federation.config.ts'))
|
|
15
|
+
? ['.']
|
|
16
|
+
: defaultAppDirs;
|
|
17
|
+
|
|
18
|
+
for (const appDir of appDirs) {
|
|
19
|
+
const configPath = path.join(root, appDir, 'module-federation.config.ts');
|
|
20
|
+
if (!fs.existsSync(configPath)) {
|
|
21
|
+
throw new Error(
|
|
22
|
+
`Missing Module Federation config: ${path.relative(root, configPath)}`,
|
|
23
|
+
);
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const config = fs.readFileSync(configPath, 'utf-8');
|
|
27
|
+
if (!config.includes('exposes:') || config.includes('dts: false')) {
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
const typesArchivePath = path.join(root, appDir, 'dist/@mf-types.zip');
|
|
32
|
+
if (!fs.existsSync(typesArchivePath)) {
|
|
33
|
+
throw new Error(
|
|
34
|
+
`Missing Module Federation DTS archive: ${path.relative(root, typesArchivePath)}`,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const stats = fs.statSync(typesArchivePath);
|
|
39
|
+
if (stats.size === 0) {
|
|
40
|
+
throw new Error(
|
|
41
|
+
`Empty Module Federation DTS archive: ${path.relative(root, typesArchivePath)}`,
|
|
42
|
+
);
|
|
43
|
+
}
|
|
44
|
+
}
|