@bleedingdev/modern-js-create 3.2.0-ultramodern.102 → 3.2.0-ultramodern.104
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 +9 -3
- package/dist/index.js +709 -128
- package/dist/types/locale/index.d.ts +117 -2
- package/dist/types/ultramodern-package-source.d.ts +28 -0
- package/dist/types/ultramodern-workspace.d.ts +1 -2
- package/package.json +3 -3
- package/template/README.md +6 -0
- package/template/config/favicon.svg +5 -0
- package/template/config/public/assets/ultramodern-logo.svg +6 -0
- package/template/config/public/locales/cs/translation.json +5 -0
- package/template/config/public/locales/en/translation.json +5 -0
- package/template/modern.config.ts.handlebars +8 -2
- package/template/scripts/check-i18n-strings.mjs +114 -2
- package/template/scripts/validate-ultramodern.mjs.handlebars +133 -40
- package/template/src/routes/[lang]/page.tsx.handlebars +39 -41
- package/template/src/routes/index.css.handlebars +192 -55
- package/template/tests/ultramodern.contract.test.ts.handlebars +71 -8
- package/template/tsconfig.json +1 -1
- package/template-workspace/README.md.handlebars +12 -3
|
@@ -1,3 +1,118 @@
|
|
|
1
|
-
|
|
2
|
-
declare const
|
|
1
|
+
import { I18n } from '@modern-js/i18n-utils';
|
|
2
|
+
declare const i18n: I18n;
|
|
3
|
+
declare const localeKeys: {
|
|
4
|
+
prompt: {
|
|
5
|
+
projectName: string;
|
|
6
|
+
};
|
|
7
|
+
error: {
|
|
8
|
+
projectNameEmpty: string;
|
|
9
|
+
directoryExists: string;
|
|
10
|
+
invalidRouter: string;
|
|
11
|
+
invalidBffRuntime: string;
|
|
12
|
+
createFailed: string;
|
|
13
|
+
};
|
|
14
|
+
message: {
|
|
15
|
+
welcome: string;
|
|
16
|
+
success: string;
|
|
17
|
+
nextSteps: string;
|
|
18
|
+
step1: string;
|
|
19
|
+
step2: string;
|
|
20
|
+
step3: string;
|
|
21
|
+
};
|
|
22
|
+
help: {
|
|
23
|
+
title: string;
|
|
24
|
+
description: string;
|
|
25
|
+
usage: string;
|
|
26
|
+
usageExample: string;
|
|
27
|
+
options: string;
|
|
28
|
+
optionHelp: string;
|
|
29
|
+
optionVersion: string;
|
|
30
|
+
optionLang: string;
|
|
31
|
+
optionRouter: string;
|
|
32
|
+
optionBff: string;
|
|
33
|
+
optionBffRuntime: string;
|
|
34
|
+
optionTailwind: string;
|
|
35
|
+
optionWorkspace: string;
|
|
36
|
+
optionUltramodernWorkspace: string;
|
|
37
|
+
optionUltramodernPackageSource: string;
|
|
38
|
+
optionUltramodernPackageScope: string;
|
|
39
|
+
optionUltramodernPackageNamePrefix: string;
|
|
40
|
+
optionVertical: string;
|
|
41
|
+
optionSub: string;
|
|
42
|
+
examples: string;
|
|
43
|
+
example1: string;
|
|
44
|
+
example2: string;
|
|
45
|
+
example3: string;
|
|
46
|
+
example4: string;
|
|
47
|
+
example5: string;
|
|
48
|
+
example6: string;
|
|
49
|
+
example7: string;
|
|
50
|
+
example8: string;
|
|
51
|
+
example9: string;
|
|
52
|
+
example10: string;
|
|
53
|
+
example11: string;
|
|
54
|
+
example12: string;
|
|
55
|
+
moreInfo: string;
|
|
56
|
+
};
|
|
57
|
+
version: {
|
|
58
|
+
message: string;
|
|
59
|
+
};
|
|
60
|
+
} | {
|
|
61
|
+
prompt: {
|
|
62
|
+
projectName: string;
|
|
63
|
+
};
|
|
64
|
+
error: {
|
|
65
|
+
projectNameEmpty: string;
|
|
66
|
+
directoryExists: string;
|
|
67
|
+
invalidRouter: string;
|
|
68
|
+
invalidBffRuntime: string;
|
|
69
|
+
createFailed: string;
|
|
70
|
+
};
|
|
71
|
+
message: {
|
|
72
|
+
welcome: string;
|
|
73
|
+
success: string;
|
|
74
|
+
nextSteps: string;
|
|
75
|
+
step1: string;
|
|
76
|
+
step2: string;
|
|
77
|
+
step3: string;
|
|
78
|
+
};
|
|
79
|
+
help: {
|
|
80
|
+
title: string;
|
|
81
|
+
description: string;
|
|
82
|
+
usage: string;
|
|
83
|
+
usageExample: string;
|
|
84
|
+
options: string;
|
|
85
|
+
optionHelp: string;
|
|
86
|
+
optionVersion: string;
|
|
87
|
+
optionLang: string;
|
|
88
|
+
optionRouter: string;
|
|
89
|
+
optionBff: string;
|
|
90
|
+
optionBffRuntime: string;
|
|
91
|
+
optionTailwind: string;
|
|
92
|
+
optionWorkspace: string;
|
|
93
|
+
optionUltramodernWorkspace: string;
|
|
94
|
+
optionUltramodernPackageSource: string;
|
|
95
|
+
optionUltramodernPackageScope: string;
|
|
96
|
+
optionUltramodernPackageNamePrefix: string;
|
|
97
|
+
optionVertical: string;
|
|
98
|
+
optionSub: string;
|
|
99
|
+
examples: string;
|
|
100
|
+
example1: string;
|
|
101
|
+
example2: string;
|
|
102
|
+
example3: string;
|
|
103
|
+
example4: string;
|
|
104
|
+
example5: string;
|
|
105
|
+
example6: string;
|
|
106
|
+
example7: string;
|
|
107
|
+
example8: string;
|
|
108
|
+
example9: string;
|
|
109
|
+
example10: string;
|
|
110
|
+
example11: string;
|
|
111
|
+
example12: string;
|
|
112
|
+
moreInfo: string;
|
|
113
|
+
};
|
|
114
|
+
version: {
|
|
115
|
+
message: string;
|
|
116
|
+
};
|
|
117
|
+
};
|
|
3
118
|
export { i18n, localeKeys };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
export declare const WORKSPACE_PACKAGE_VERSION = "workspace:*";
|
|
2
|
+
export declare const BLEEDINGDEV_CREATE_PACKAGE = "@bleedingdev/modern-js-create";
|
|
3
|
+
export declare const BLEEDINGDEV_PACKAGE_SCOPE = "bleedingdev";
|
|
4
|
+
export declare const BLEEDINGDEV_PACKAGE_NAME_PREFIX = "modern-js-";
|
|
5
|
+
export declare const BLEEDINGDEV_FRAMEWORK_VERSION_ENV = "MODERN_CREATE_ULTRAMODERN_FRAMEWORK_VERSION";
|
|
6
|
+
export declare const ULTRAMODERN_SINGLE_APP_MODERN_PACKAGES: readonly ['@modern-js/runtime', '@modern-js/app-tools', '@modern-js/tsconfig', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/plugin-bff', '@modern-js/adapter-rstest'];
|
|
7
|
+
export declare const ULTRAMODERN_WORKSPACE_MODERN_PACKAGES: readonly ['@modern-js/app-tools', '@modern-js/plugin-bff', '@modern-js/plugin-i18n', '@modern-js/plugin-tanstack', '@modern-js/runtime'];
|
|
8
|
+
export type UltramodernPackageSourceStrategy = 'workspace' | 'install';
|
|
9
|
+
export type ResolvedUltramodernPackageSource = {
|
|
10
|
+
strategy: UltramodernPackageSourceStrategy;
|
|
11
|
+
modernPackageVersion: string;
|
|
12
|
+
registry?: string;
|
|
13
|
+
aliasScope?: string;
|
|
14
|
+
aliasPackageNamePrefix?: string;
|
|
15
|
+
};
|
|
16
|
+
export type UltramodernModernPackagesMetadata = {
|
|
17
|
+
packages: string[];
|
|
18
|
+
specifier: string;
|
|
19
|
+
registry?: string;
|
|
20
|
+
aliases?: Record<string, string>;
|
|
21
|
+
};
|
|
22
|
+
export declare function modernPackageVersion(packageSource: ResolvedUltramodernPackageSource): string;
|
|
23
|
+
export declare function modernAliasPackageName(packageName: string, packageSource: ResolvedUltramodernPackageSource): string;
|
|
24
|
+
export declare function modernPackageSpecifier(packageName: string, packageSource: ResolvedUltramodernPackageSource): string;
|
|
25
|
+
export declare function modernPackageAliases(packageNames: readonly string[], packageSource: ResolvedUltramodernPackageSource): Record<string, string> | undefined;
|
|
26
|
+
export declare function createModernPackagesMetadata(packageNames: readonly string[], packageSource: ResolvedUltramodernPackageSource, options?: {
|
|
27
|
+
includeAliases?: boolean;
|
|
28
|
+
}): UltramodernModernPackagesMetadata;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
type UltramodernPackageSourceStrategy
|
|
1
|
+
import { type UltramodernPackageSourceStrategy } from './ultramodern-package-source';
|
|
2
2
|
export type UltramodernWorkspaceOptions = {
|
|
3
3
|
targetDir: string;
|
|
4
4
|
packageName: string;
|
|
@@ -28,4 +28,3 @@ export declare const ultramodernWorkspaceVersions: {
|
|
|
28
28
|
tailwind: string;
|
|
29
29
|
tailwindPostcss: string;
|
|
30
30
|
};
|
|
31
|
-
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.104",
|
|
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.9.1",
|
|
42
42
|
"@typescript/native-preview": "7.0.0-dev.20260527.2",
|
|
43
43
|
"tsx": "^4.22.3",
|
|
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.104"
|
|
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.104"
|
|
58
58
|
}
|
|
59
59
|
}
|
package/template/README.md
CHANGED
|
@@ -49,6 +49,7 @@ The default app is intentionally monolith-friendly:
|
|
|
49
49
|
| --- | --- |
|
|
50
50
|
| App routes | Locale-prefixed pages under `src/routes/[lang]` |
|
|
51
51
|
| Copy | English and Czech resources in `config/public/locales` |
|
|
52
|
+
| Web defaults | Local favicon/logo assets, localized metadata, semantic starter markup |
|
|
52
53
|
| Styling | App-local CSS, with Tailwind files only when selected |
|
|
53
54
|
| Server logic | Optional BFF entrypoints under `api/` |
|
|
54
55
|
| Tests | Rstest smoke coverage in `tests/` |
|
|
@@ -66,6 +67,11 @@ real routes, actions, and API calls. Put user-visible text in
|
|
|
66
67
|
`config/public/locales/<lang>/translation.json`, then render it through
|
|
67
68
|
`react-i18next` or `@modern-js/plugin-i18n/runtime`.
|
|
68
69
|
|
|
70
|
+
The starter keeps favicon and logo assets local in `config/favicon.svg` and
|
|
71
|
+
`config/public/assets/ultramodern-logo.svg`. Replace those files when your app
|
|
72
|
+
has product branding. The localized page title and description live in the same
|
|
73
|
+
translation resources as the visible UI copy.
|
|
74
|
+
|
|
69
75
|
Tune the preset in `modern.config.ts`. Production builds require
|
|
70
76
|
`MODERN_PUBLIC_SITE_URL` so canonical and `hreflang` URLs use your deployed
|
|
71
77
|
origin. The local fallback is `http://localhost:8080`.
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="UltraModern.js">
|
|
2
|
+
<rect width="64" height="64" rx="12" fill="#111827"/>
|
|
3
|
+
<path fill="#35d399" d="M14 17h9v21c0 5 3 8 9 8s9-3 9-8V17h9v22c0 11-8 18-18 18s-18-7-18-18V17Z"/>
|
|
4
|
+
<path fill="#ffffff" d="M28 17h8v25h-8z"/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 160" role="img" aria-label="UltraModern.js logo">
|
|
2
|
+
<rect width="160" height="160" rx="28" fill="#111827"/>
|
|
3
|
+
<path fill="#35d399" d="M42 36h22v56c0 22 14 34 36 34s36-12 36-34V36h22v57c0 35-24 57-58 57S42 128 42 93V36Z"/>
|
|
4
|
+
<path fill="#ffffff" d="M76 36h20v74H76z"/>
|
|
5
|
+
<path fill="#8be8ff" d="M112 36h18v74h-18z"/>
|
|
6
|
+
</svg>
|
|
@@ -33,7 +33,12 @@
|
|
|
33
33
|
"switcher": "Jazyk"
|
|
34
34
|
},
|
|
35
35
|
"logoAlt": "Logo UltraModern.js",
|
|
36
|
+
"meta": {
|
|
37
|
+
"description": "Lokalizovany starter aplikace UltraModern.js se silnymi vychozimi hodnotami pro komplexni produkty.",
|
|
38
|
+
"title": "UltraModern.js Starter"
|
|
39
|
+
},
|
|
36
40
|
"name": "Jednoduchy starter aplikace",
|
|
41
|
+
"skipLink": "Preskocit na obsah",
|
|
37
42
|
"title": "UltraModern.js Starter"
|
|
38
43
|
}
|
|
39
44
|
}
|
|
@@ -33,7 +33,12 @@
|
|
|
33
33
|
"switcher": "Language"
|
|
34
34
|
},
|
|
35
35
|
"logoAlt": "UltraModern.js Logo",
|
|
36
|
+
"meta": {
|
|
37
|
+
"description": "A localized UltraModern.js app starter with strong defaults for complex products.",
|
|
38
|
+
"title": "UltraModern.js Starter"
|
|
39
|
+
},
|
|
36
40
|
"name": "Simple app starter",
|
|
41
|
+
"skipLink": "Skip to content",
|
|
37
42
|
"title": "UltraModern.js Starter"
|
|
38
43
|
}
|
|
39
44
|
}
|
|
@@ -39,7 +39,13 @@ export default defineConfig(
|
|
|
39
39
|
{{/if}} runtimeFramework: '{{bffRuntime}}',
|
|
40
40
|
},
|
|
41
41
|
|
|
42
|
-
{{/if}}
|
|
42
|
+
{{/if}} html: {
|
|
43
|
+
meta: {
|
|
44
|
+
viewport: 'width=device-width, initial-scale=1.0, viewport-fit=cover',
|
|
45
|
+
},
|
|
46
|
+
title: 'UltraModern.js Starter',
|
|
47
|
+
},
|
|
48
|
+
plugins: [
|
|
43
49
|
appTools(),
|
|
44
50
|
i18nPlugin({
|
|
45
51
|
localeDetection: {
|
|
@@ -64,8 +70,8 @@ export default defineConfig(
|
|
|
64
70
|
enableBffRequestId,
|
|
65
71
|
enableModuleFederationSSR,
|
|
66
72
|
enableTelemetryExporters,
|
|
67
|
-
telemetryFailLoudStartup,
|
|
68
73
|
...(typeof otlpEndpoint === 'string' ? { otlpEndpoint } : {}),
|
|
74
|
+
telemetryFailLoudStartup,
|
|
69
75
|
...(typeof victoriaMetricsEndpoint === 'string' ? { victoriaMetricsEndpoint } : {}),
|
|
70
76
|
},
|
|
71
77
|
),
|
|
@@ -7,6 +7,97 @@ const ignoredDirectories = new Set(['.modern', '.modernjs', 'dist', 'node_module
|
|
|
7
7
|
const visibleAttributePattern =
|
|
8
8
|
/\s(?:aria-label|alt|placeholder|title)=["']([^"']*[A-Za-z][^"']*)["']/gu;
|
|
9
9
|
const jsxTextPattern = />([^<>{}]*[A-Za-z][^<>{}]*)</gu;
|
|
10
|
+
const jsxIntrinsicTags = new Set([
|
|
11
|
+
'a',
|
|
12
|
+
'abbr',
|
|
13
|
+
'address',
|
|
14
|
+
'area',
|
|
15
|
+
'article',
|
|
16
|
+
'aside',
|
|
17
|
+
'audio',
|
|
18
|
+
'b',
|
|
19
|
+
'blockquote',
|
|
20
|
+
'body',
|
|
21
|
+
'br',
|
|
22
|
+
'button',
|
|
23
|
+
'canvas',
|
|
24
|
+
'caption',
|
|
25
|
+
'cite',
|
|
26
|
+
'code',
|
|
27
|
+
'col',
|
|
28
|
+
'colgroup',
|
|
29
|
+
'data',
|
|
30
|
+
'datalist',
|
|
31
|
+
'dd',
|
|
32
|
+
'del',
|
|
33
|
+
'details',
|
|
34
|
+
'dfn',
|
|
35
|
+
'dialog',
|
|
36
|
+
'div',
|
|
37
|
+
'dl',
|
|
38
|
+
'dt',
|
|
39
|
+
'em',
|
|
40
|
+
'fieldset',
|
|
41
|
+
'figcaption',
|
|
42
|
+
'figure',
|
|
43
|
+
'footer',
|
|
44
|
+
'form',
|
|
45
|
+
'h1',
|
|
46
|
+
'h2',
|
|
47
|
+
'h3',
|
|
48
|
+
'h4',
|
|
49
|
+
'h5',
|
|
50
|
+
'h6',
|
|
51
|
+
'head',
|
|
52
|
+
'header',
|
|
53
|
+
'hr',
|
|
54
|
+
'html',
|
|
55
|
+
'i',
|
|
56
|
+
'iframe',
|
|
57
|
+
'img',
|
|
58
|
+
'input',
|
|
59
|
+
'label',
|
|
60
|
+
'legend',
|
|
61
|
+
'li',
|
|
62
|
+
'link',
|
|
63
|
+
'main',
|
|
64
|
+
'mark',
|
|
65
|
+
'menu',
|
|
66
|
+
'meta',
|
|
67
|
+
'meter',
|
|
68
|
+
'nav',
|
|
69
|
+
'ol',
|
|
70
|
+
'option',
|
|
71
|
+
'p',
|
|
72
|
+
'picture',
|
|
73
|
+
'pre',
|
|
74
|
+
'progress',
|
|
75
|
+
'q',
|
|
76
|
+
'script',
|
|
77
|
+
'section',
|
|
78
|
+
'select',
|
|
79
|
+
'small',
|
|
80
|
+
'source',
|
|
81
|
+
'span',
|
|
82
|
+
'strong',
|
|
83
|
+
'style',
|
|
84
|
+
'summary',
|
|
85
|
+
'svg',
|
|
86
|
+
'table',
|
|
87
|
+
'tbody',
|
|
88
|
+
'td',
|
|
89
|
+
'template',
|
|
90
|
+
'textarea',
|
|
91
|
+
'tfoot',
|
|
92
|
+
'th',
|
|
93
|
+
'thead',
|
|
94
|
+
'time',
|
|
95
|
+
'title',
|
|
96
|
+
'tr',
|
|
97
|
+
'u',
|
|
98
|
+
'ul',
|
|
99
|
+
'video',
|
|
100
|
+
]);
|
|
10
101
|
|
|
11
102
|
const collectFiles = (directory) => {
|
|
12
103
|
if (!fs.existsSync(directory)) {
|
|
@@ -37,6 +128,24 @@ const isCodeElementText = (content, index) => {
|
|
|
37
128
|
}
|
|
38
129
|
return /^<code(?:\s|>)/u.test(content.slice(tagStart, index));
|
|
39
130
|
};
|
|
131
|
+
const isJsxTagEnd = (content, index) => {
|
|
132
|
+
const tagStart = content.lastIndexOf('<', index);
|
|
133
|
+
if (tagStart === -1 || content.slice(tagStart + 1, index).includes('<')) {
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
const match = content
|
|
137
|
+
.slice(tagStart, index + 1)
|
|
138
|
+
.match(/^<\/?\s*([A-Za-z][\w:.-]*)\b[^<>]*>$/u);
|
|
139
|
+
if (!match) {
|
|
140
|
+
return false;
|
|
141
|
+
}
|
|
142
|
+
const [, tagName] = match;
|
|
143
|
+
return (
|
|
144
|
+
/^[A-Z]/u.test(tagName) ||
|
|
145
|
+
tagName.includes('-') ||
|
|
146
|
+
jsxIntrinsicTags.has(tagName)
|
|
147
|
+
);
|
|
148
|
+
};
|
|
40
149
|
const isIgnoredLine = (content, index) => {
|
|
41
150
|
const lineStart = content.lastIndexOf('\n', index) + 1;
|
|
42
151
|
const lineEnd = content.indexOf('\n', index);
|
|
@@ -54,20 +163,23 @@ const violations = [];
|
|
|
54
163
|
for (const filePath of scanRoots.flatMap(collectFiles)) {
|
|
55
164
|
const content = fs.readFileSync(filePath, 'utf-8');
|
|
56
165
|
for (const match of content.matchAll(visibleAttributePattern)) {
|
|
166
|
+
const [, visibleText] = match;
|
|
57
167
|
if (!isIgnoredLine(content, match.index ?? 0)) {
|
|
58
168
|
violations.push({
|
|
59
169
|
filePath,
|
|
60
170
|
line: lineNumberForIndex(content, match.index ?? 0),
|
|
61
|
-
text:
|
|
171
|
+
text: visibleText.trim(),
|
|
62
172
|
});
|
|
63
173
|
}
|
|
64
174
|
}
|
|
65
175
|
|
|
66
176
|
for (const match of content.matchAll(jsxTextPattern)) {
|
|
67
|
-
const
|
|
177
|
+
const [, jsxText] = match;
|
|
178
|
+
const text = jsxText.replaceAll(/\s+/gu, ' ').trim();
|
|
68
179
|
if (
|
|
69
180
|
text &&
|
|
70
181
|
!isIgnoredLine(content, match.index ?? 0) &&
|
|
182
|
+
isJsxTagEnd(content, match.index ?? 0) &&
|
|
71
183
|
!isCodeElementText(content, match.index ?? 0)
|
|
72
184
|
) {
|
|
73
185
|
violations.push({
|
|
@@ -8,6 +8,9 @@ const packageSourcePath = path.resolve(
|
|
|
8
8
|
process.cwd(),
|
|
9
9
|
'.modernjs/ultramodern-package-source.json',
|
|
10
10
|
);
|
|
11
|
+
const readText = (relativePath) =>
|
|
12
|
+
fs.readFileSync(path.resolve(process.cwd(), relativePath), 'utf-8');
|
|
13
|
+
const readJson = (relativePath) => JSON.parse(readText(relativePath));
|
|
11
14
|
const readPnpmConfig = (key) => {
|
|
12
15
|
const env = Object.fromEntries(
|
|
13
16
|
Object.entries(process.env).filter(
|
|
@@ -109,6 +112,8 @@ const requiredPaths = [
|
|
|
109
112
|
{{/if}}
|
|
110
113
|
'config/public/locales/en/translation.json',
|
|
111
114
|
'config/public/locales/cs/translation.json',
|
|
115
|
+
'config/favicon.svg',
|
|
116
|
+
'config/public/assets/ultramodern-logo.svg',
|
|
112
117
|
'src/modern-app-env.d.ts',
|
|
113
118
|
'src/routes/index.css',
|
|
114
119
|
'src/routes/layout.tsx',
|
|
@@ -241,6 +246,11 @@ const packageJson = JSON.parse(
|
|
|
241
246
|
fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),
|
|
242
247
|
);
|
|
243
248
|
const packageSource = JSON.parse(fs.readFileSync(packageSourcePath, 'utf-8'));
|
|
249
|
+
const routePage = readText('src/routes/[lang]/page.tsx');
|
|
250
|
+
const routeCss = readText('src/routes/index.css');
|
|
251
|
+
const modernConfig = readText('modern.config.ts');
|
|
252
|
+
const enLocale = readJson('config/public/locales/en/translation.json');
|
|
253
|
+
const csLocale = readJson('config/public/locales/cs/translation.json');
|
|
244
254
|
const unresolvedTemplateMarker = String.fromCodePoint(123, 123);
|
|
245
255
|
if (JSON.stringify(packageJson).includes(unresolvedTemplateMarker)) {
|
|
246
256
|
console.error('package.json contains unresolved template markers');
|
|
@@ -250,6 +260,89 @@ if (JSON.stringify(packageSource).includes(unresolvedTemplateMarker)) {
|
|
|
250
260
|
console.error('package source metadata contains unresolved template markers');
|
|
251
261
|
process.exit(1);
|
|
252
262
|
}
|
|
263
|
+
|
|
264
|
+
for (const [fileName, text] of [
|
|
265
|
+
['src/routes/[lang]/page.tsx', routePage],
|
|
266
|
+
['src/routes/index.css', routeCss],
|
|
267
|
+
['modern.config.ts', modernConfig],
|
|
268
|
+
]) {
|
|
269
|
+
if (text.includes('lf3-static.bytednsdoc.com')) {
|
|
270
|
+
console.error(`${fileName} must not depend on remote starter assets`);
|
|
271
|
+
process.exit(1);
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
for (const requiredSnippet of [
|
|
276
|
+
'<Helmet',
|
|
277
|
+
'htmlAttributes={{',
|
|
278
|
+
'dir: languageDirections[currentLanguage]',
|
|
279
|
+
'lang: currentLanguage',
|
|
280
|
+
'<title>{pageTitle}</title>',
|
|
281
|
+
'<meta name="description" content={pageDescription} />',
|
|
282
|
+
'<main id="starter-main" className="starter-main">',
|
|
283
|
+
'<h1 id="starter-heading" className="title">',
|
|
284
|
+
'src="/assets/ultramodern-logo.svg"',
|
|
285
|
+
'height={96}',
|
|
286
|
+
'width={96}',
|
|
287
|
+
'<span aria-hidden="true" className="arrow-right" />',
|
|
288
|
+
]) {
|
|
289
|
+
if (!routePage.includes(requiredSnippet)) {
|
|
290
|
+
console.error(`Generated route page must retain starter correctness snippet: ${requiredSnippet}`);
|
|
291
|
+
process.exit(1);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
for (const forbiddenSnippet of ['src="https://', '<div className="title">']) {
|
|
296
|
+
if (routePage.includes(forbiddenSnippet)) {
|
|
297
|
+
console.error(`Generated route page must not contain ${forbiddenSnippet}`);
|
|
298
|
+
process.exit(1);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
for (const requiredSnippet of [
|
|
303
|
+
'width=device-width, initial-scale=1.0, viewport-fit=cover',
|
|
304
|
+
"title: 'UltraModern.js Starter'",
|
|
305
|
+
]) {
|
|
306
|
+
if (!modernConfig.includes(requiredSnippet)) {
|
|
307
|
+
console.error(`modern.config.ts must retain ${requiredSnippet}`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
if (modernConfig.includes('user-scalable=no') || modernConfig.includes('maximum-scale')) {
|
|
312
|
+
console.error('modern.config.ts must not disable user zoom');
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
for (const requiredSnippet of [
|
|
317
|
+
'min-block-size: 100dvh',
|
|
318
|
+
'grid-template-columns: repeat(auto-fit, minmax(min(100%, 17rem), 1fr))',
|
|
319
|
+
':focus-visible',
|
|
320
|
+
'@media (prefers-reduced-motion: reduce)',
|
|
321
|
+
'.skip-link',
|
|
322
|
+
]) {
|
|
323
|
+
if (!routeCss.includes(requiredSnippet)) {
|
|
324
|
+
console.error(`Starter CSS must retain ${requiredSnippet}`);
|
|
325
|
+
process.exit(1);
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
if (routeCss.includes('width: 1100px') || /\.card:focus(?!-visible)/u.test(routeCss)) {
|
|
329
|
+
console.error('Starter CSS must not reintroduce fixed grid width or focus transform styling');
|
|
330
|
+
process.exit(1);
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
for (const [localeName, locale] of [
|
|
334
|
+
['en', enLocale],
|
|
335
|
+
['cs', csLocale],
|
|
336
|
+
]) {
|
|
337
|
+
if (
|
|
338
|
+
typeof locale.home?.meta?.title !== 'string' ||
|
|
339
|
+
typeof locale.home?.meta?.description !== 'string' ||
|
|
340
|
+
typeof locale.home?.skipLink !== 'string'
|
|
341
|
+
) {
|
|
342
|
+
console.error(`${localeName} locale must include starter metadata and skip-link copy`);
|
|
343
|
+
process.exit(1);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
253
346
|
{{#unless isSubproject}}
|
|
254
347
|
const skillsLock = JSON.parse(
|
|
255
348
|
fs.readFileSync(path.resolve(process.cwd(), '.agents/skills-lock.json'), 'utf-8'),
|
|
@@ -388,6 +481,11 @@ if (
|
|
|
388
481
|
process.exit(1);
|
|
389
482
|
}
|
|
390
483
|
|
|
484
|
+
if (packageJson.modernjs?.packageSource?.strategy !== packageSource.strategy) {
|
|
485
|
+
console.error('package.json package source strategy must match package source metadata');
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
|
|
391
489
|
if (packageSource.schemaVersion !== 1) {
|
|
392
490
|
console.error('Package source metadata must use schemaVersion 1');
|
|
393
491
|
process.exit(1);
|
|
@@ -403,21 +501,33 @@ if (packageSource.strategy !== 'workspace' && packageSource.strategy !== 'instal
|
|
|
403
501
|
process.exit(1);
|
|
404
502
|
}
|
|
405
503
|
|
|
406
|
-
const
|
|
407
|
-
|
|
408
|
-
'
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
'@modern-js/plugin-tanstack',
|
|
412
|
-
'@modern-js/plugin-bff',
|
|
413
|
-
'@modern-js/adapter-rstest',
|
|
414
|
-
];
|
|
504
|
+
const declaredModernPackages = packageSource.modernPackages?.packages;
|
|
505
|
+
if (!Array.isArray(declaredModernPackages) || declaredModernPackages.length === 0) {
|
|
506
|
+
console.error('Package source metadata must declare the generated Modern package cohort');
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
415
509
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
}
|
|
510
|
+
const invalidModernPackages = declaredModernPackages.filter(
|
|
511
|
+
(packageName) => typeof packageName !== 'string' || !packageName.startsWith('@modern-js/'),
|
|
512
|
+
);
|
|
513
|
+
if (invalidModernPackages.length > 0) {
|
|
514
|
+
console.error(`Package source metadata contains invalid Modern packages: ${invalidModernPackages.join(', ')}`);
|
|
515
|
+
process.exit(1);
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
const packageJsonModernDependencies = [
|
|
519
|
+
...new Set(
|
|
520
|
+
['dependencies', 'devDependencies']
|
|
521
|
+
.flatMap((section) => Object.keys(packageJson[section] ?? {}))
|
|
522
|
+
.filter((packageName) => packageName.startsWith('@modern-js/')),
|
|
523
|
+
),
|
|
524
|
+
];
|
|
525
|
+
const missingMetadataPackages = packageJsonModernDependencies.filter(
|
|
526
|
+
(packageName) => !declaredModernPackages.includes(packageName),
|
|
527
|
+
);
|
|
528
|
+
if (missingMetadataPackages.length > 0) {
|
|
529
|
+
console.error(`Package source metadata must include package.json Modern dependencies: ${missingMetadataPackages.join(', ')}`);
|
|
530
|
+
process.exit(1);
|
|
421
531
|
}
|
|
422
532
|
|
|
423
533
|
const expectedModernSpecifier = packageSource.modernPackages?.specifier;
|
|
@@ -442,32 +552,15 @@ const expectedModernDependency = (packageName) => {
|
|
|
442
552
|
: expectedModernSpecifier;
|
|
443
553
|
};
|
|
444
554
|
|
|
445
|
-
for (const
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
])
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
console.error(`Dependency ${packageName} must match package source metadata`);
|
|
455
|
-
process.exit(1);
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
for (const packageName of [
|
|
460
|
-
'@modern-js/app-tools',
|
|
461
|
-
'@modern-js/adapter-rstest',
|
|
462
|
-
'@modern-js/tsconfig',
|
|
463
|
-
'@modern-js/plugin-bff',
|
|
464
|
-
]) {
|
|
465
|
-
if (
|
|
466
|
-
packageJson.devDependencies?.[packageName] &&
|
|
467
|
-
packageJson.devDependencies[packageName] !== expectedModernDependency(packageName)
|
|
468
|
-
) {
|
|
469
|
-
console.error(`Dev dependency ${packageName} must match package source metadata`);
|
|
470
|
-
process.exit(1);
|
|
555
|
+
for (const section of ['dependencies', 'devDependencies']) {
|
|
556
|
+
for (const packageName of declaredModernPackages) {
|
|
557
|
+
if (
|
|
558
|
+
packageJson[section]?.[packageName] &&
|
|
559
|
+
packageJson[section][packageName] !== expectedModernDependency(packageName)
|
|
560
|
+
) {
|
|
561
|
+
console.error(`${section}.${packageName} must match package source metadata`);
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
471
564
|
}
|
|
472
565
|
}
|
|
473
566
|
|