@bleedingdev/modern-js-create 3.2.0-ultramodern.11 → 3.2.0-ultramodern.111
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 +158 -35
- package/bin/run.js +0 -0
- package/dist/cjs/index.cjs +1042 -0
- package/dist/cjs/locale/en.cjs +97 -0
- package/dist/cjs/locale/index.cjs +50 -0
- package/dist/cjs/locale/zh.cjs +97 -0
- package/dist/cjs/ultramodern-package-source.cjs +135 -0
- package/dist/cjs/ultramodern-workspace.cjs +5623 -0
- package/dist/esm/index.js +1004 -0
- package/dist/esm/locale/en.js +59 -0
- package/dist/esm/locale/index.js +9 -0
- package/dist/esm/locale/zh.js +59 -0
- package/dist/esm/ultramodern-package-source.js +63 -0
- package/dist/esm/ultramodern-workspace.js +5561 -0
- package/dist/esm-node/index.js +1005 -0
- package/dist/esm-node/locale/en.js +60 -0
- package/dist/esm-node/locale/index.js +10 -0
- package/dist/esm-node/locale/zh.js +60 -0
- package/dist/esm-node/ultramodern-package-source.js +64 -0
- package/dist/esm-node/ultramodern-workspace.js +5562 -0
- package/dist/types/locale/en.d.ts +3 -0
- package/dist/types/locale/index.d.ts +117 -2
- package/dist/types/locale/zh.d.ts +3 -0
- package/dist/types/ultramodern-package-source.d.ts +28 -0
- package/dist/types/ultramodern-workspace.d.ts +12 -2
- package/package.json +27 -11
- package/template/.codex/hooks.json +16 -0
- package/template/.github/renovate.json +53 -0
- package/template/.github/workflows/ultramodern-gates.yml.handlebars +34 -10
- package/template/.mise.toml.handlebars +2 -0
- package/template/AGENTS.md +9 -6
- package/template/README.md +66 -34
- package/template/api/effect/index.ts.handlebars +20 -9
- package/template/api/lambda/hello.ts.handlebars +5 -5
- 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 +44 -0
- package/template/config/public/locales/en/translation.json +44 -0
- package/template/lefthook.yml +10 -0
- package/template/modern.config.ts.handlebars +35 -3
- package/template/oxfmt.config.ts +8 -1
- package/template/oxlint.config.ts +8 -1
- package/template/package.json.handlebars +37 -30
- package/template/pnpm-workspace.yaml +34 -0
- package/template/rstest.config.mts +5 -0
- package/template/scripts/bootstrap-agent-skills.mjs +148 -15
- package/template/scripts/check-i18n-strings.mjs +3 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +495 -3
- package/template/src/modern-app-env.d.ts +2 -0
- package/template/src/modern.runtime.ts.handlebars +17 -1
- package/template/src/routes/[lang]/page.tsx.handlebars +209 -0
- package/template/src/routes/index.css.handlebars +192 -55
- package/template/src/routes/layout.tsx.handlebars +2 -1
- package/template/tailwind.config.ts.handlebars +1 -1
- package/template/tests/tsconfig.json +7 -0
- package/template/tests/ultramodern.contract.test.ts.handlebars +163 -0
- package/template/tsconfig.json +2 -1
- package/template-workspace/.agents/agent-reference-repos.json +24 -0
- package/template-workspace/.agents/skills-lock.json +19 -0
- package/template-workspace/.codex/hooks.json +16 -0
- package/template-workspace/.github/renovate.json +29 -0
- package/template-workspace/.github/workflows/ultramodern-workspace-gates.yml.handlebars +54 -0
- package/template-workspace/.gitignore.handlebars +5 -0
- package/template-workspace/.mise.toml.handlebars +2 -0
- package/template-workspace/AGENTS.md +36 -5
- package/template-workspace/README.md.handlebars +70 -11
- package/template-workspace/lefthook.yml +10 -0
- package/template-workspace/oxfmt.config.ts +1 -0
- package/template-workspace/oxlint.config.ts +1 -0
- package/template-workspace/pnpm-workspace.yaml +31 -8
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +190 -21
- package/template-workspace/scripts/setup-agent-reference-repos.mjs +370 -0
- package/dist/index.js +0 -2474
- package/template/src/routes/page.tsx.handlebars +0 -136
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +0 -405
|
@@ -1,8 +1,44 @@
|
|
|
1
|
+
import { execFileSync } from 'node:child_process';
|
|
1
2
|
import fs from 'node:fs';
|
|
2
3
|
import path from 'node:path';
|
|
3
4
|
|
|
4
5
|
const configPath = path.resolve(process.cwd(), 'modern.config.ts');
|
|
5
6
|
const templateManifestPath = path.resolve(process.cwd(), '.modernjs/mv-template-manifest.json');
|
|
7
|
+
const packageSourcePath = path.resolve(
|
|
8
|
+
process.cwd(),
|
|
9
|
+
'.modernjs/ultramodern-package-source.json',
|
|
10
|
+
);
|
|
11
|
+
const readText = (relativePath) =>
|
|
12
|
+
fs.readFileSync(path.resolve(process.cwd(), relativePath), 'utf-8');
|
|
13
|
+
const readJson = (relativePath) => JSON.parse(readText(relativePath));
|
|
14
|
+
const readPnpmConfig = (key) => {
|
|
15
|
+
const env = Object.fromEntries(
|
|
16
|
+
Object.entries(process.env).filter(
|
|
17
|
+
([envKey]) => !/^(?:npm|pnpm)_config_/iu.test(envKey),
|
|
18
|
+
),
|
|
19
|
+
);
|
|
20
|
+
const output = execFileSync('pnpm', ['config', 'get', key, '--json'], {
|
|
21
|
+
cwd: process.cwd(),
|
|
22
|
+
encoding: 'utf-8',
|
|
23
|
+
env,
|
|
24
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
25
|
+
}).trim();
|
|
26
|
+
return output ? JSON.parse(output) : undefined;
|
|
27
|
+
};
|
|
28
|
+
const enableTailwind = {{enableTailwind}};
|
|
29
|
+
const expectedPnpmVersion = '{{pnpmVersion}}';
|
|
30
|
+
const activePnpmVersion = execFileSync('pnpm', ['--version'], {
|
|
31
|
+
cwd: process.cwd(),
|
|
32
|
+
encoding: 'utf-8',
|
|
33
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
34
|
+
}).trim();
|
|
35
|
+
|
|
36
|
+
if (activePnpmVersion !== expectedPnpmVersion) {
|
|
37
|
+
console.error(
|
|
38
|
+
`Generated app requires pnpm ${expectedPnpmVersion}; active pnpm is ${activePnpmVersion}. Run mise install, then rerun pnpm from the activated shell`,
|
|
39
|
+
);
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
6
42
|
|
|
7
43
|
if (!fs.existsSync(configPath)) {
|
|
8
44
|
console.error('modern.config.ts not found');
|
|
@@ -16,6 +52,11 @@ const requiredTokens = [
|
|
|
16
52
|
'enableModuleFederationSSR',
|
|
17
53
|
'enableBffRequestId',
|
|
18
54
|
'enableTelemetryExporters',
|
|
55
|
+
'i18nPlugin(',
|
|
56
|
+
'localePathRedirect: true',
|
|
57
|
+
'ULTRAMODERN_SITE_URL',
|
|
58
|
+
'MODERN_PUBLIC_SITE_URL must be set for production builds',
|
|
59
|
+
'globalVars',
|
|
19
60
|
];
|
|
20
61
|
const missing = requiredTokens.filter((token) => !content.includes(token));
|
|
21
62
|
|
|
@@ -32,7 +73,6 @@ if (!fs.existsSync(templateManifestPath)) {
|
|
|
32
73
|
const templateManifest = JSON.parse(fs.readFileSync(templateManifestPath, 'utf-8'));
|
|
33
74
|
const requiredDeniedPaths = [
|
|
34
75
|
'.git/**',
|
|
35
|
-
'.github/**',
|
|
36
76
|
'.npmrc',
|
|
37
77
|
'.yarnrc',
|
|
38
78
|
'.env',
|
|
@@ -42,15 +82,43 @@ const requiredDeniedPaths = [
|
|
|
42
82
|
];
|
|
43
83
|
const requiredPostMaterialization = [
|
|
44
84
|
'ultramodern-contract-check',
|
|
45
|
-
'
|
|
85
|
+
'agent-skill-postinstall-allowed',
|
|
86
|
+
'github-workflow-security-enforced',
|
|
87
|
+
'package-source-retained',
|
|
88
|
+
'pnpm-11-policy-enforced',
|
|
89
|
+
'rstest-smoke-tests',
|
|
46
90
|
'template-manifest-retained',
|
|
47
91
|
];
|
|
48
92
|
const requiredPaths = [
|
|
93
|
+
{{#unless isSubproject}}
|
|
49
94
|
'AGENTS.md',
|
|
50
95
|
'.agents/skills-lock.json',
|
|
96
|
+
'.codex/hooks.json',
|
|
97
|
+
'.github/renovate.json',
|
|
98
|
+
'.github/workflows/ultramodern-gates.yml',
|
|
99
|
+
'lefthook.yml',
|
|
100
|
+
'scripts/bootstrap-agent-skills.mjs',
|
|
101
|
+
{{/unless}}
|
|
102
|
+
'.mise.toml',
|
|
103
|
+
'.modernjs/ultramodern-package-source.json',
|
|
51
104
|
'oxlint.config.ts',
|
|
52
105
|
'oxfmt.config.ts',
|
|
53
|
-
'
|
|
106
|
+
'pnpm-workspace.yaml',
|
|
107
|
+
'rstest.config.mts',
|
|
108
|
+
'scripts/check-i18n-strings.mjs',
|
|
109
|
+
{{#if enableTailwind}}
|
|
110
|
+
'postcss.config.mjs',
|
|
111
|
+
'tailwind.config.ts',
|
|
112
|
+
{{/if}}
|
|
113
|
+
'config/public/locales/en/translation.json',
|
|
114
|
+
'config/public/locales/cs/translation.json',
|
|
115
|
+
'config/favicon.svg',
|
|
116
|
+
'config/public/assets/ultramodern-logo.svg',
|
|
117
|
+
'src/modern-app-env.d.ts',
|
|
118
|
+
'src/routes/index.css',
|
|
119
|
+
'src/routes/layout.tsx',
|
|
120
|
+
'src/routes/[lang]/page.tsx',
|
|
121
|
+
'tests/ultramodern.contract.test.ts',
|
|
54
122
|
];
|
|
55
123
|
const manifestErrors = [];
|
|
56
124
|
|
|
@@ -61,6 +129,70 @@ for (const requiredPath of requiredPaths) {
|
|
|
61
129
|
}
|
|
62
130
|
}
|
|
63
131
|
|
|
132
|
+
if (fs.existsSync(path.resolve(process.cwd(), 'src/routes/page.tsx'))) {
|
|
133
|
+
console.error('src/routes/page.tsx must move under src/routes/[lang]/page.tsx');
|
|
134
|
+
process.exit(1);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (
|
|
138
|
+
!enableTailwind &&
|
|
139
|
+
(fs.existsSync(path.resolve(process.cwd(), 'postcss.config.mjs')) ||
|
|
140
|
+
fs.existsSync(path.resolve(process.cwd(), 'tailwind.config.ts')))
|
|
141
|
+
) {
|
|
142
|
+
console.error('Tailwind config files must not be written when Tailwind is disabled');
|
|
143
|
+
process.exit(1);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
{{#unless isSubproject}}
|
|
147
|
+
const workflowContent = fs.readFileSync(
|
|
148
|
+
path.resolve(process.cwd(), '.github/workflows/ultramodern-gates.yml'),
|
|
149
|
+
'utf-8',
|
|
150
|
+
);
|
|
151
|
+
const renovateConfig = JSON.parse(
|
|
152
|
+
fs.readFileSync(path.resolve(process.cwd(), '.github/renovate.json'), 'utf-8'),
|
|
153
|
+
);
|
|
154
|
+
for (const requiredSnippet of [
|
|
155
|
+
'permissions:\n contents: read',
|
|
156
|
+
'pull_request:',
|
|
157
|
+
'persist-credentials: false',
|
|
158
|
+
'jdx/mise-action',
|
|
159
|
+
'pnpm install --frozen-lockfile',
|
|
160
|
+
'pnpm run ultramodern:check',
|
|
161
|
+
'pnpm run build',
|
|
162
|
+
'MODERN_PUBLIC_SITE_URL: http://localhost:8080',
|
|
163
|
+
'timeout-minutes:',
|
|
164
|
+
'egress-policy: audit',
|
|
165
|
+
]) {
|
|
166
|
+
if (!workflowContent.includes(requiredSnippet)) {
|
|
167
|
+
console.error(`Generated workflow must retain ${requiredSnippet.split('\n')[0]}`);
|
|
168
|
+
process.exit(1);
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
if (workflowContent.includes('pull_request_target')) {
|
|
172
|
+
console.error('Generated workflow must not use pull_request_target');
|
|
173
|
+
process.exit(1);
|
|
174
|
+
}
|
|
175
|
+
for (const match of workflowContent.matchAll(/^\s*uses:\s*([^@\s]+)@([^\s#]+)/gmu)) {
|
|
176
|
+
const [, actionName, actionRef] = match;
|
|
177
|
+
if (!/^[a-f0-9]{40}$/u.test(actionRef)) {
|
|
178
|
+
console.error(`Generated workflow must pin ${actionName}@${actionRef} to a commit SHA`);
|
|
179
|
+
process.exit(1);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
if (
|
|
183
|
+
renovateConfig.dependencyDashboard !== true ||
|
|
184
|
+
renovateConfig.minimumReleaseAge !== '1 day' ||
|
|
185
|
+
!renovateConfig.extends?.includes('helpers:pinGitHubActionDigests') ||
|
|
186
|
+
!renovateConfig.packageRules?.some(
|
|
187
|
+
(rule) =>
|
|
188
|
+
rule.dependencyDashboardApproval === true && rule.matchUpdateTypes?.includes('major'),
|
|
189
|
+
)
|
|
190
|
+
) {
|
|
191
|
+
console.error('Generated Renovate config must retain dashboard, release-age, action pinning, and major-approval policy');
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
{{/unless}}
|
|
195
|
+
|
|
64
196
|
if (templateManifest.schemaVersion !== 1) {
|
|
65
197
|
manifestErrors.push('schemaVersion');
|
|
66
198
|
}
|
|
@@ -90,6 +222,12 @@ for (const deniedPath of requiredDeniedPaths) {
|
|
|
90
222
|
if (templateManifest.lifecyclePolicy?.denyByDefault !== true) {
|
|
91
223
|
manifestErrors.push('lifecyclePolicy.denyByDefault');
|
|
92
224
|
}
|
|
225
|
+
if (
|
|
226
|
+
JSON.stringify(templateManifest.lifecyclePolicy?.allowedScripts) !==
|
|
227
|
+
JSON.stringify(['postinstall'])
|
|
228
|
+
) {
|
|
229
|
+
manifestErrors.push('lifecyclePolicy.allowedScripts');
|
|
230
|
+
}
|
|
93
231
|
|
|
94
232
|
for (const token of requiredPostMaterialization) {
|
|
95
233
|
if (!templateManifest.validation?.postMaterializationValidation?.includes(token)) {
|
|
@@ -107,21 +245,121 @@ if (manifestErrors.length > 0) {
|
|
|
107
245
|
const packageJson = JSON.parse(
|
|
108
246
|
fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),
|
|
109
247
|
);
|
|
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');
|
|
110
254
|
const unresolvedTemplateMarker = String.fromCodePoint(123, 123);
|
|
111
255
|
if (JSON.stringify(packageJson).includes(unresolvedTemplateMarker)) {
|
|
112
256
|
console.error('package.json contains unresolved template markers');
|
|
113
257
|
process.exit(1);
|
|
114
258
|
}
|
|
259
|
+
if (JSON.stringify(packageSource).includes(unresolvedTemplateMarker)) {
|
|
260
|
+
console.error('package source metadata contains unresolved template markers');
|
|
261
|
+
process.exit(1);
|
|
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
|
+
}
|
|
346
|
+
{{#unless isSubproject}}
|
|
115
347
|
const skillsLock = JSON.parse(
|
|
116
348
|
fs.readFileSync(path.resolve(process.cwd(), '.agents/skills-lock.json'), 'utf-8'),
|
|
117
349
|
);
|
|
350
|
+
{{/unless}}
|
|
118
351
|
const requiredScripts = {
|
|
119
352
|
format: 'oxfmt .',
|
|
120
353
|
'format:check': 'oxfmt --check .',
|
|
354
|
+
'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
|
|
121
355
|
lint: 'oxlint .',
|
|
122
356
|
'lint:fix': 'oxlint . --fix',
|
|
357
|
+
{{#unless isSubproject}}
|
|
358
|
+
postinstall: 'oxfmt . && node ./scripts/bootstrap-agent-skills.mjs',
|
|
123
359
|
'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
|
|
124
360
|
'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
|
|
361
|
+
{{/unless}}
|
|
362
|
+
test: 'rstest run',
|
|
125
363
|
};
|
|
126
364
|
|
|
127
365
|
for (const [scriptName, scriptCommand] of Object.entries(requiredScripts)) {
|
|
@@ -131,6 +369,15 @@ for (const [scriptName, scriptCommand] of Object.entries(requiredScripts)) {
|
|
|
131
369
|
}
|
|
132
370
|
}
|
|
133
371
|
|
|
372
|
+
const i18nCheckScript = readText('scripts/check-i18n-strings.mjs');
|
|
373
|
+
if (
|
|
374
|
+
!i18nCheckScript.includes("from '@modern-js/code-tools'") ||
|
|
375
|
+
!i18nCheckScript.includes('runSingleAppI18nCheck')
|
|
376
|
+
) {
|
|
377
|
+
console.error('i18n:check must call @modern-js/code-tools');
|
|
378
|
+
process.exit(1);
|
|
379
|
+
}
|
|
380
|
+
|
|
134
381
|
if (
|
|
135
382
|
!packageJson.scripts?.typecheck?.includes('effect-tsgo') ||
|
|
136
383
|
!packageJson.scripts.typecheck.includes('get-exe-path')
|
|
@@ -139,12 +386,235 @@ if (
|
|
|
139
386
|
process.exit(1);
|
|
140
387
|
}
|
|
141
388
|
|
|
389
|
+
const expectedUltramodernCheck =
|
|
390
|
+
'pnpm format:check && pnpm lint && pnpm typecheck && pnpm i18n:check && pnpm test{{#unless isSubproject}} && pnpm skills:check{{/unless}} && node ./scripts/validate-ultramodern.mjs';
|
|
391
|
+
if (packageJson.scripts?.['ultramodern:check'] !== expectedUltramodernCheck) {
|
|
392
|
+
console.error('ultramodern:check must run format, lint, typecheck, i18n, tests, and contract validation');
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (packageJson.private !== true) {
|
|
397
|
+
console.error('Generated app package must be private by default');
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const miseConfig = fs.readFileSync(path.resolve(process.cwd(), '.mise.toml'), 'utf-8');
|
|
402
|
+
if (!miseConfig.includes(`pnpm = "${expectedPnpmVersion}"`)) {
|
|
403
|
+
console.error(`Generated app must pin pnpm ${expectedPnpmVersion} in .mise.toml`);
|
|
404
|
+
process.exit(1);
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
if (packageJson.packageManager !== `pnpm@${expectedPnpmVersion}`) {
|
|
408
|
+
console.error(`Generated app package must pin pnpm@${expectedPnpmVersion}`);
|
|
409
|
+
process.exit(1);
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (packageJson.engines?.pnpm !== `>=${expectedPnpmVersion} <11.6.0`) {
|
|
413
|
+
console.error(`Generated app package must require pnpm >=${expectedPnpmVersion} <11.6.0`);
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
if (packageJson.pnpm !== undefined) {
|
|
418
|
+
console.error('Generated app must keep pnpm policy in pnpm-workspace.yaml, not package.json');
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
if (readPnpmConfig('minimumReleaseAge') !== 1440) {
|
|
423
|
+
console.error('pnpm minimumReleaseAge must be 1440');
|
|
424
|
+
process.exit(1);
|
|
425
|
+
}
|
|
426
|
+
if (readPnpmConfig('minimumReleaseAgeStrict') !== true) {
|
|
427
|
+
console.error('pnpm minimumReleaseAgeStrict must be true');
|
|
428
|
+
process.exit(1);
|
|
429
|
+
}
|
|
430
|
+
if (readPnpmConfig('minimumReleaseAgeIgnoreMissingTime') !== false) {
|
|
431
|
+
console.error('pnpm minimumReleaseAgeIgnoreMissingTime must be false');
|
|
432
|
+
process.exit(1);
|
|
433
|
+
}
|
|
434
|
+
const expectedMinimumReleaseAgeExcludes = [
|
|
435
|
+
'@bleedingdev/modern-js-*',
|
|
436
|
+
'@tanstack/react-router',
|
|
437
|
+
'@tanstack/router-core',
|
|
438
|
+
'@typescript/native-preview',
|
|
439
|
+
'@typescript/native-preview-*',
|
|
440
|
+
'@types/react',
|
|
441
|
+
];
|
|
442
|
+
if (
|
|
443
|
+
JSON.stringify(readPnpmConfig('minimumReleaseAgeExclude')) !==
|
|
444
|
+
JSON.stringify(expectedMinimumReleaseAgeExcludes)
|
|
445
|
+
) {
|
|
446
|
+
console.error('pnpm minimumReleaseAgeExclude must allow only approved latest-lane package cohorts');
|
|
447
|
+
process.exit(1);
|
|
448
|
+
}
|
|
449
|
+
if (readPnpmConfig('trustPolicy') !== 'no-downgrade') {
|
|
450
|
+
console.error('pnpm trustPolicy must be no-downgrade');
|
|
451
|
+
process.exit(1);
|
|
452
|
+
}
|
|
453
|
+
for (const [key, expected] of [
|
|
454
|
+
['trustPolicyIgnoreAfter', 1440],
|
|
455
|
+
['blockExoticSubdeps', true],
|
|
456
|
+
['engineStrict', true],
|
|
457
|
+
['pmOnFail', 'error'],
|
|
458
|
+
['verifyDepsBeforeRun', 'error'],
|
|
459
|
+
['strictDepBuilds', true],
|
|
460
|
+
]) {
|
|
461
|
+
if (readPnpmConfig(key) !== expected) {
|
|
462
|
+
console.error(`pnpm ${key} must be ${String(expected)}`);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
if (
|
|
467
|
+
JSON.stringify(readPnpmConfig('allowBuilds')) !==
|
|
468
|
+
JSON.stringify({
|
|
469
|
+
'@swc/core': true,
|
|
470
|
+
'core-js': true,
|
|
471
|
+
esbuild: true,
|
|
472
|
+
lefthook: true,
|
|
473
|
+
'msgpackr-extract': true,
|
|
474
|
+
sharp: true,
|
|
475
|
+
workerd: true,
|
|
476
|
+
})
|
|
477
|
+
) {
|
|
478
|
+
console.error('pnpm allowBuilds must approve only the generated app build dependencies');
|
|
479
|
+
process.exit(1);
|
|
480
|
+
}
|
|
481
|
+
if (
|
|
482
|
+
JSON.stringify(readPnpmConfig('onlyBuiltDependencies')) !==
|
|
483
|
+
JSON.stringify(['@swc/core', 'core-js', 'esbuild', 'lefthook', 'msgpackr-extract', 'sharp', 'workerd'])
|
|
484
|
+
) {
|
|
485
|
+
console.error('pnpm onlyBuiltDependencies must approve only the generated app build dependencies');
|
|
486
|
+
process.exit(1);
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
if (packageJson.modernjs?.preset !== 'presetUltramodern') {
|
|
490
|
+
console.error('package.json must declare presetUltramodern metadata');
|
|
491
|
+
process.exit(1);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
if (
|
|
495
|
+
packageJson.modernjs?.packageSource?.config !== './.modernjs/ultramodern-package-source.json'
|
|
496
|
+
) {
|
|
497
|
+
console.error('package.json must retain package source metadata location');
|
|
498
|
+
process.exit(1);
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (packageJson.modernjs?.packageSource?.strategy !== packageSource.strategy) {
|
|
502
|
+
console.error('package.json package source strategy must match package source metadata');
|
|
503
|
+
process.exit(1);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (packageSource.schemaVersion !== 1) {
|
|
507
|
+
console.error('Package source metadata must use schemaVersion 1');
|
|
508
|
+
process.exit(1);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (packageSource.preset !== 'presetUltramodern') {
|
|
512
|
+
console.error('Package source metadata must declare presetUltramodern');
|
|
513
|
+
process.exit(1);
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (packageSource.strategy !== 'workspace' && packageSource.strategy !== 'install') {
|
|
517
|
+
console.error('Package source strategy must be workspace or install');
|
|
518
|
+
process.exit(1);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
const declaredModernPackages = packageSource.modernPackages?.packages;
|
|
522
|
+
if (!Array.isArray(declaredModernPackages) || declaredModernPackages.length === 0) {
|
|
523
|
+
console.error('Package source metadata must declare the generated Modern package cohort');
|
|
524
|
+
process.exit(1);
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
const invalidModernPackages = declaredModernPackages.filter(
|
|
528
|
+
(packageName) => typeof packageName !== 'string' || !packageName.startsWith('@modern-js/'),
|
|
529
|
+
);
|
|
530
|
+
if (invalidModernPackages.length > 0) {
|
|
531
|
+
console.error(`Package source metadata contains invalid Modern packages: ${invalidModernPackages.join(', ')}`);
|
|
532
|
+
process.exit(1);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
const packageJsonModernDependencies = [
|
|
536
|
+
...new Set(
|
|
537
|
+
['dependencies', 'devDependencies']
|
|
538
|
+
.flatMap((section) => Object.keys(packageJson[section] ?? {}))
|
|
539
|
+
.filter((packageName) => packageName.startsWith('@modern-js/')),
|
|
540
|
+
),
|
|
541
|
+
];
|
|
542
|
+
const missingMetadataPackages = packageJsonModernDependencies.filter(
|
|
543
|
+
(packageName) => !declaredModernPackages.includes(packageName),
|
|
544
|
+
);
|
|
545
|
+
if (missingMetadataPackages.length > 0) {
|
|
546
|
+
console.error(`Package source metadata must include package.json Modern dependencies: ${missingMetadataPackages.join(', ')}`);
|
|
547
|
+
process.exit(1);
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const expectedModernSpecifier = packageSource.modernPackages?.specifier;
|
|
551
|
+
if (typeof expectedModernSpecifier !== 'string' || expectedModernSpecifier.length === 0) {
|
|
552
|
+
console.error('Package source metadata must provide a Modern package specifier');
|
|
553
|
+
process.exit(1);
|
|
554
|
+
}
|
|
555
|
+
if (
|
|
556
|
+
packageSource.strategy === 'install' &&
|
|
557
|
+
expectedModernSpecifier !== templateManifest.template?.version
|
|
558
|
+
) {
|
|
559
|
+
console.error(
|
|
560
|
+
`Package source Modern specifier ${expectedModernSpecifier} must match template version ${templateManifest.template?.version}`,
|
|
561
|
+
);
|
|
562
|
+
process.exit(1);
|
|
563
|
+
}
|
|
564
|
+
|
|
565
|
+
const expectedModernDependency = (packageName) => {
|
|
566
|
+
const alias = packageSource.modernPackages?.aliases?.[packageName];
|
|
567
|
+
return typeof alias === 'string'
|
|
568
|
+
? `npm:${alias}@${expectedModernSpecifier}`
|
|
569
|
+
: expectedModernSpecifier;
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
for (const section of ['dependencies', 'devDependencies']) {
|
|
573
|
+
for (const packageName of declaredModernPackages) {
|
|
574
|
+
if (
|
|
575
|
+
packageJson[section]?.[packageName] &&
|
|
576
|
+
packageJson[section][packageName] !== expectedModernDependency(packageName)
|
|
577
|
+
) {
|
|
578
|
+
console.error(`${section}.${packageName} must match package source metadata`);
|
|
579
|
+
process.exit(1);
|
|
580
|
+
}
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
if (
|
|
585
|
+
packageJson.devDependencies?.['@modern-js/create'] !==
|
|
586
|
+
expectedModernDependency('@modern-js/create')
|
|
587
|
+
) {
|
|
588
|
+
console.error('devDependencies.@modern-js/create must match package source metadata');
|
|
589
|
+
process.exit(1);
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
for (const dependency of ['@modern-js/plugin-i18n', 'i18next', 'react-i18next']) {
|
|
593
|
+
if (!packageJson.dependencies?.[dependency]) {
|
|
594
|
+
console.error(`Missing dependency: ${dependency}`);
|
|
595
|
+
process.exit(1);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
142
599
|
for (const dependency of [
|
|
143
600
|
'@effect/tsgo',
|
|
601
|
+
'@modern-js/adapter-rstest',
|
|
602
|
+
'@modern-js/code-tools',
|
|
603
|
+
'@modern-js/create',
|
|
604
|
+
'@rstest/core',
|
|
144
605
|
'@typescript/native-preview',
|
|
606
|
+
'happy-dom',
|
|
607
|
+
{{#if enableTailwind}}
|
|
608
|
+
'@tailwindcss/postcss',
|
|
609
|
+
'postcss',
|
|
610
|
+
'tailwindcss',
|
|
611
|
+
{{/if}}
|
|
145
612
|
'oxlint',
|
|
146
613
|
'oxfmt',
|
|
147
614
|
'ultracite',
|
|
615
|
+
{{#unless isSubproject}}
|
|
616
|
+
'lefthook',
|
|
617
|
+
{{/unless}}
|
|
148
618
|
]) {
|
|
149
619
|
if (!packageJson.devDependencies?.[dependency]) {
|
|
150
620
|
console.error(`Missing devDependency: ${dependency}`);
|
|
@@ -152,6 +622,27 @@ for (const dependency of [
|
|
|
152
622
|
}
|
|
153
623
|
}
|
|
154
624
|
|
|
625
|
+
{{#if enableTailwind}}
|
|
626
|
+
if (
|
|
627
|
+
packageJson.devDependencies?.tailwindcss !== '^4.3.0' ||
|
|
628
|
+
packageJson.devDependencies?.['@tailwindcss/postcss'] !== '^4.3.0'
|
|
629
|
+
) {
|
|
630
|
+
console.error('Tailwind CSS dependencies must use the UltraModern default baseline');
|
|
631
|
+
process.exit(1);
|
|
632
|
+
}
|
|
633
|
+
{{/if}}
|
|
634
|
+
{{#unless enableTailwind}}
|
|
635
|
+
if (
|
|
636
|
+
packageJson.devDependencies?.tailwindcss ||
|
|
637
|
+
packageJson.devDependencies?.['@tailwindcss/postcss'] ||
|
|
638
|
+
packageJson.devDependencies?.postcss
|
|
639
|
+
) {
|
|
640
|
+
console.error('Tailwind CSS dependencies must be absent when Tailwind is disabled');
|
|
641
|
+
process.exit(1);
|
|
642
|
+
}
|
|
643
|
+
{{/unless}}
|
|
644
|
+
|
|
645
|
+
{{#unless isSubproject}}
|
|
155
646
|
const privateSource = skillsLock.sources?.find(
|
|
156
647
|
(source) => source.repository === 'https://github.com/TechsioCZ/skills',
|
|
157
648
|
);
|
|
@@ -162,5 +653,6 @@ for (const skillName of ['plan-graph', 'dag', 'subagent-graph', 'helm', 'debugge
|
|
|
162
653
|
process.exit(1);
|
|
163
654
|
}
|
|
164
655
|
}
|
|
656
|
+
{{/unless}}
|
|
165
657
|
|
|
166
658
|
console.log('Ultramodern contract check passed.');
|
|
@@ -1,7 +1,23 @@
|
|
|
1
1
|
import { defineRuntimeConfig } from '@modern-js/runtime';
|
|
2
|
+
import { createInstance } from 'i18next';
|
|
3
|
+
|
|
4
|
+
const i18nInstance = createInstance();
|
|
2
5
|
|
|
3
6
|
export default defineRuntimeConfig({
|
|
7
|
+
i18n: {
|
|
8
|
+
i18nInstance,
|
|
9
|
+
initOptions: {
|
|
10
|
+
defaultNS: 'translation',
|
|
11
|
+
fallbackLng: 'en',
|
|
12
|
+
interpolation: {
|
|
13
|
+
escapeValue: false,
|
|
14
|
+
},
|
|
15
|
+
ns: ['translation'],
|
|
16
|
+
supportedLngs: ['en', 'cs'],
|
|
17
|
+
},
|
|
18
|
+
},
|
|
4
19
|
{{#if isTanstackRouter}} router: {
|
|
5
20
|
framework: 'tanstack',
|
|
6
|
-
},
|
|
21
|
+
},
|
|
22
|
+
{{/if~}}
|
|
7
23
|
});
|