@bleedingdev/modern-js-create 3.2.0-ultramodern.8 → 3.2.0-ultramodern.81
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 +143 -35
- package/dist/index.js +4587 -607
- package/dist/types/locale/en.d.ts +3 -0
- package/dist/types/locale/zh.d.ts +3 -0
- package/dist/types/ultramodern-workspace.d.ts +11 -0
- package/package.json +6 -6
- 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 +60 -34
- package/template/api/effect/index.ts.handlebars +9 -15
- package/template/config/public/locales/cs/translation.json +39 -0
- package/template/config/public/locales/en/translation.json +39 -0
- package/template/lefthook.yml +10 -0
- package/template/modern.config.ts.handlebars +41 -21
- package/template/oxfmt.config.ts +4 -3
- package/template/oxlint.config.ts +4 -4
- package/template/package.json.handlebars +43 -34
- package/template/pnpm-workspace.yaml +19 -0
- package/template/rstest.config.mts +7 -0
- package/template/scripts/bootstrap-agent-skills.mjs +63 -31
- package/template/scripts/check-i18n-strings.mjs +83 -0
- package/template/scripts/validate-ultramodern.mjs.handlebars +373 -35
- package/template/shared/effect/api.ts.handlebars +1 -2
- package/template/src/modern-app-env.d.ts +2 -0
- package/template/src/modern.runtime.ts.handlebars +17 -3
- package/template/src/routes/[lang]/page.tsx.handlebars +212 -0
- package/template/src/routes/index.css.handlebars +14 -3
- package/template/src/routes/layout.tsx.handlebars +2 -1
- package/template/tests/tsconfig.json +7 -0
- package/template/tests/ultramodern.contract.test.ts.handlebars +78 -0
- 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 +33 -10
- package/template-workspace/lefthook.yml +10 -0
- package/template-workspace/oxfmt.config.ts +13 -3
- package/template-workspace/oxlint.config.ts +12 -4
- package/template-workspace/pnpm-workspace.yaml +20 -8
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +79 -22
- package/template-workspace/scripts/setup-agent-reference-repos.mjs +368 -0
- package/template/src/routes/page.tsx.handlebars +0 -119
- package/template-workspace/scripts/validate-ultramodern-workspace.mjs.handlebars +0 -403
|
@@ -1,31 +1,66 @@
|
|
|
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
|
-
const templateManifestPath = path.resolve(
|
|
6
|
+
const templateManifestPath = path.resolve(process.cwd(), '.modernjs/mv-template-manifest.json');
|
|
7
|
+
const packageSourcePath = path.resolve(
|
|
6
8
|
process.cwd(),
|
|
7
|
-
'.modernjs/
|
|
9
|
+
'.modernjs/ultramodern-package-source.json',
|
|
8
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
|
+
};
|
|
26
|
+
const isSubproject = {{isSubproject}};
|
|
27
|
+
const enableTailwind = {{enableTailwind}};
|
|
28
|
+
const expectedPnpmVersion = '{{pnpmVersion}}';
|
|
29
|
+
const activePnpmVersion = execFileSync('pnpm', ['--version'], {
|
|
30
|
+
cwd: process.cwd(),
|
|
31
|
+
encoding: 'utf-8',
|
|
32
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
33
|
+
}).trim();
|
|
34
|
+
|
|
35
|
+
if (activePnpmVersion !== expectedPnpmVersion) {
|
|
36
|
+
console.error(
|
|
37
|
+
`Generated app requires pnpm ${expectedPnpmVersion}; active pnpm is ${activePnpmVersion}. Run mise install, then rerun pnpm from the activated shell`,
|
|
38
|
+
);
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
9
41
|
|
|
10
42
|
if (!fs.existsSync(configPath)) {
|
|
11
43
|
console.error('modern.config.ts not found');
|
|
12
44
|
process.exit(1);
|
|
13
45
|
}
|
|
14
46
|
|
|
15
|
-
const content = fs.readFileSync(configPath, '
|
|
47
|
+
const content = fs.readFileSync(configPath, 'utf-8');
|
|
16
48
|
const requiredTokens = [
|
|
17
49
|
'presetUltramodern(',
|
|
18
50
|
'appTools()',
|
|
19
51
|
'enableModuleFederationSSR',
|
|
20
52
|
'enableBffRequestId',
|
|
21
53
|
'enableTelemetryExporters',
|
|
54
|
+
'i18nPlugin(',
|
|
55
|
+
'localePathRedirect: true',
|
|
56
|
+
'ULTRAMODERN_SITE_URL',
|
|
57
|
+
'MODERN_PUBLIC_SITE_URL must be set for production builds',
|
|
58
|
+
'globalVars',
|
|
22
59
|
];
|
|
23
|
-
const missing = requiredTokens.filter(token => !content.includes(token));
|
|
60
|
+
const missing = requiredTokens.filter((token) => !content.includes(token));
|
|
24
61
|
|
|
25
62
|
if (missing.length > 0) {
|
|
26
|
-
console.error(
|
|
27
|
-
`Ultramodern contract check failed. Missing tokens: ${missing.join(', ')}`,
|
|
28
|
-
);
|
|
63
|
+
console.error(`Ultramodern contract check failed. Missing tokens: ${missing.join(', ')}`);
|
|
29
64
|
process.exit(1);
|
|
30
65
|
}
|
|
31
66
|
|
|
@@ -34,12 +69,9 @@ if (!fs.existsSync(templateManifestPath)) {
|
|
|
34
69
|
process.exit(1);
|
|
35
70
|
}
|
|
36
71
|
|
|
37
|
-
const templateManifest = JSON.parse(
|
|
38
|
-
fs.readFileSync(templateManifestPath, 'utf8'),
|
|
39
|
-
);
|
|
72
|
+
const templateManifest = JSON.parse(fs.readFileSync(templateManifestPath, 'utf-8'));
|
|
40
73
|
const requiredDeniedPaths = [
|
|
41
74
|
'.git/**',
|
|
42
|
-
'.github/**',
|
|
43
75
|
'.npmrc',
|
|
44
76
|
'.yarnrc',
|
|
45
77
|
'.env',
|
|
@@ -49,15 +81,41 @@ const requiredDeniedPaths = [
|
|
|
49
81
|
];
|
|
50
82
|
const requiredPostMaterialization = [
|
|
51
83
|
'ultramodern-contract-check',
|
|
52
|
-
'
|
|
84
|
+
'agent-skill-postinstall-allowed',
|
|
85
|
+
'github-workflow-security-enforced',
|
|
86
|
+
'package-source-retained',
|
|
87
|
+
'pnpm-11-policy-enforced',
|
|
88
|
+
'rstest-smoke-tests',
|
|
53
89
|
'template-manifest-retained',
|
|
54
90
|
];
|
|
55
91
|
const requiredPaths = [
|
|
92
|
+
{{#unless isSubproject}}
|
|
56
93
|
'AGENTS.md',
|
|
57
94
|
'.agents/skills-lock.json',
|
|
95
|
+
'.codex/hooks.json',
|
|
96
|
+
'.github/renovate.json',
|
|
97
|
+
'.github/workflows/ultramodern-gates.yml',
|
|
98
|
+
'lefthook.yml',
|
|
58
99
|
'oxlint.config.ts',
|
|
59
100
|
'oxfmt.config.ts',
|
|
60
101
|
'scripts/bootstrap-agent-skills.mjs',
|
|
102
|
+
{{/unless}}
|
|
103
|
+
'.mise.toml',
|
|
104
|
+
'.modernjs/ultramodern-package-source.json',
|
|
105
|
+
'pnpm-workspace.yaml',
|
|
106
|
+
'rstest.config.mts',
|
|
107
|
+
'scripts/check-i18n-strings.mjs',
|
|
108
|
+
{{#if enableTailwind}}
|
|
109
|
+
'postcss.config.mjs',
|
|
110
|
+
'tailwind.config.ts',
|
|
111
|
+
{{/if}}
|
|
112
|
+
'config/public/locales/en/translation.json',
|
|
113
|
+
'config/public/locales/cs/translation.json',
|
|
114
|
+
'src/modern-app-env.d.ts',
|
|
115
|
+
'src/routes/index.css',
|
|
116
|
+
'src/routes/layout.tsx',
|
|
117
|
+
'src/routes/[lang]/page.tsx',
|
|
118
|
+
'tests/ultramodern.contract.test.ts',
|
|
61
119
|
];
|
|
62
120
|
const manifestErrors = [];
|
|
63
121
|
|
|
@@ -68,6 +126,71 @@ for (const requiredPath of requiredPaths) {
|
|
|
68
126
|
}
|
|
69
127
|
}
|
|
70
128
|
|
|
129
|
+
if (fs.existsSync(path.resolve(process.cwd(), 'src/routes/page.tsx'))) {
|
|
130
|
+
console.error('src/routes/page.tsx must move under src/routes/[lang]/page.tsx');
|
|
131
|
+
process.exit(1);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
if (!enableTailwind) {
|
|
135
|
+
if (
|
|
136
|
+
fs.existsSync(path.resolve(process.cwd(), 'postcss.config.mjs')) ||
|
|
137
|
+
fs.existsSync(path.resolve(process.cwd(), 'tailwind.config.ts'))
|
|
138
|
+
) {
|
|
139
|
+
console.error('Tailwind config files must not be written when Tailwind is disabled');
|
|
140
|
+
process.exit(1);
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
{{#unless isSubproject}}
|
|
145
|
+
const workflowContent = fs.readFileSync(
|
|
146
|
+
path.resolve(process.cwd(), '.github/workflows/ultramodern-gates.yml'),
|
|
147
|
+
'utf-8',
|
|
148
|
+
);
|
|
149
|
+
const renovateConfig = JSON.parse(
|
|
150
|
+
fs.readFileSync(path.resolve(process.cwd(), '.github/renovate.json'), 'utf-8'),
|
|
151
|
+
);
|
|
152
|
+
for (const requiredSnippet of [
|
|
153
|
+
'permissions:\n contents: read',
|
|
154
|
+
'pull_request:',
|
|
155
|
+
'persist-credentials: false',
|
|
156
|
+
'jdx/mise-action',
|
|
157
|
+
'pnpm install --frozen-lockfile',
|
|
158
|
+
'pnpm run ultramodern:check',
|
|
159
|
+
'pnpm run build',
|
|
160
|
+
'MODERN_PUBLIC_SITE_URL: http://localhost:8080',
|
|
161
|
+
'timeout-minutes:',
|
|
162
|
+
'egress-policy: audit',
|
|
163
|
+
]) {
|
|
164
|
+
if (!workflowContent.includes(requiredSnippet)) {
|
|
165
|
+
console.error(`Generated workflow must retain ${requiredSnippet.split('\n')[0]}`);
|
|
166
|
+
process.exit(1);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
if (workflowContent.includes('pull_request_target')) {
|
|
170
|
+
console.error('Generated workflow must not use pull_request_target');
|
|
171
|
+
process.exit(1);
|
|
172
|
+
}
|
|
173
|
+
for (const match of workflowContent.matchAll(/^\s*uses:\s*([^@\s]+)@([^\s#]+)/gmu)) {
|
|
174
|
+
const [, actionName, actionRef] = match;
|
|
175
|
+
if (!/^[a-f0-9]{40}$/u.test(actionRef)) {
|
|
176
|
+
console.error(`Generated workflow must pin ${actionName}@${actionRef} to a commit SHA`);
|
|
177
|
+
process.exit(1);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
if (
|
|
181
|
+
renovateConfig.dependencyDashboard !== true ||
|
|
182
|
+
renovateConfig.minimumReleaseAge !== '1 day' ||
|
|
183
|
+
!renovateConfig.extends?.includes('helpers:pinGitHubActionDigests') ||
|
|
184
|
+
!renovateConfig.packageRules?.some(
|
|
185
|
+
(rule) =>
|
|
186
|
+
rule.dependencyDashboardApproval === true && rule.matchUpdateTypes?.includes('major'),
|
|
187
|
+
)
|
|
188
|
+
) {
|
|
189
|
+
console.error('Generated Renovate config must retain dashboard, release-age, action pinning, and major-approval policy');
|
|
190
|
+
process.exit(1);
|
|
191
|
+
}
|
|
192
|
+
{{/unless}}
|
|
193
|
+
|
|
71
194
|
if (templateManifest.schemaVersion !== 1) {
|
|
72
195
|
manifestErrors.push('schemaVersion');
|
|
73
196
|
}
|
|
@@ -79,10 +202,10 @@ if (templateManifest.source?.type !== 'builtin') {
|
|
|
79
202
|
if (
|
|
80
203
|
!Array.isArray(templateManifest.integrity?.checksums) ||
|
|
81
204
|
!templateManifest.integrity.checksums.some(
|
|
82
|
-
checksum =>
|
|
205
|
+
(checksum) =>
|
|
83
206
|
checksum.algorithm === 'sha256' &&
|
|
84
207
|
checksum.scope === 'source-tree' &&
|
|
85
|
-
/^[0-9a-f]{64}
|
|
208
|
+
/^[0-9a-f]{64}$/u.test(checksum.value),
|
|
86
209
|
)
|
|
87
210
|
) {
|
|
88
211
|
manifestErrors.push('integrity.checksums[source-tree]');
|
|
@@ -97,6 +220,12 @@ for (const deniedPath of requiredDeniedPaths) {
|
|
|
97
220
|
if (templateManifest.lifecyclePolicy?.denyByDefault !== true) {
|
|
98
221
|
manifestErrors.push('lifecyclePolicy.denyByDefault');
|
|
99
222
|
}
|
|
223
|
+
if (
|
|
224
|
+
JSON.stringify(templateManifest.lifecyclePolicy?.allowedScripts) !==
|
|
225
|
+
JSON.stringify(['postinstall'])
|
|
226
|
+
) {
|
|
227
|
+
manifestErrors.push('lifecyclePolicy.allowedScripts');
|
|
228
|
+
}
|
|
100
229
|
|
|
101
230
|
for (const token of requiredPostMaterialization) {
|
|
102
231
|
if (!templateManifest.validation?.postMaterializationValidation?.includes(token)) {
|
|
@@ -106,34 +235,41 @@ for (const token of requiredPostMaterialization) {
|
|
|
106
235
|
|
|
107
236
|
if (manifestErrors.length > 0) {
|
|
108
237
|
console.error(
|
|
109
|
-
`Ultramodern template manifest check failed. Invalid fields: ${manifestErrors.join(
|
|
110
|
-
', ',
|
|
111
|
-
)}`,
|
|
238
|
+
`Ultramodern template manifest check failed. Invalid fields: ${manifestErrors.join(', ')}`,
|
|
112
239
|
);
|
|
113
240
|
process.exit(1);
|
|
114
241
|
}
|
|
115
242
|
|
|
116
243
|
const packageJson = JSON.parse(
|
|
117
|
-
fs.readFileSync(path.resolve(process.cwd(), 'package.json'), '
|
|
244
|
+
fs.readFileSync(path.resolve(process.cwd(), 'package.json'), 'utf-8'),
|
|
118
245
|
);
|
|
119
|
-
const
|
|
246
|
+
const packageSource = JSON.parse(fs.readFileSync(packageSourcePath, 'utf-8'));
|
|
247
|
+
const unresolvedTemplateMarker = String.fromCodePoint(123, 123);
|
|
120
248
|
if (JSON.stringify(packageJson).includes(unresolvedTemplateMarker)) {
|
|
121
249
|
console.error('package.json contains unresolved template markers');
|
|
122
250
|
process.exit(1);
|
|
123
251
|
}
|
|
252
|
+
if (JSON.stringify(packageSource).includes(unresolvedTemplateMarker)) {
|
|
253
|
+
console.error('package source metadata contains unresolved template markers');
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
{{#unless isSubproject}}
|
|
124
257
|
const skillsLock = JSON.parse(
|
|
125
|
-
fs.readFileSync(
|
|
126
|
-
path.resolve(process.cwd(), '.agents/skills-lock.json'),
|
|
127
|
-
'utf8',
|
|
128
|
-
),
|
|
258
|
+
fs.readFileSync(path.resolve(process.cwd(), '.agents/skills-lock.json'), 'utf-8'),
|
|
129
259
|
);
|
|
260
|
+
{{/unless}}
|
|
130
261
|
const requiredScripts = {
|
|
262
|
+
'i18n:check': 'node ./scripts/check-i18n-strings.mjs',
|
|
263
|
+
test: 'rstest run',
|
|
264
|
+
{{#unless isSubproject}}
|
|
131
265
|
format: 'oxfmt .',
|
|
132
266
|
'format:check': 'oxfmt --check .',
|
|
133
267
|
lint: 'oxlint .',
|
|
134
268
|
'lint:fix': 'oxlint . --fix',
|
|
135
|
-
'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
|
|
136
269
|
'skills:check': 'node ./scripts/bootstrap-agent-skills.mjs --check',
|
|
270
|
+
'skills:install': 'node ./scripts/bootstrap-agent-skills.mjs',
|
|
271
|
+
postinstall: 'node ./scripts/bootstrap-agent-skills.mjs && (git rev-parse --is-inside-work-tree >/dev/null 2>&1 && lefthook install || true)',
|
|
272
|
+
{{/unless}}
|
|
137
273
|
};
|
|
138
274
|
|
|
139
275
|
for (const [scriptName, scriptCommand] of Object.entries(requiredScripts)) {
|
|
@@ -151,12 +287,200 @@ if (
|
|
|
151
287
|
process.exit(1);
|
|
152
288
|
}
|
|
153
289
|
|
|
290
|
+
if (!packageJson.scripts?.['ultramodern:check']?.includes('pnpm test')) {
|
|
291
|
+
console.error('ultramodern:check must run the generated Rstest suite');
|
|
292
|
+
process.exit(1);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
if (packageJson.private !== true) {
|
|
296
|
+
console.error('Generated app package must be private by default');
|
|
297
|
+
process.exit(1);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
const miseConfig = fs.readFileSync(path.resolve(process.cwd(), '.mise.toml'), 'utf-8');
|
|
301
|
+
if (!miseConfig.includes(`pnpm = "${expectedPnpmVersion}"`)) {
|
|
302
|
+
console.error(`Generated app must pin pnpm ${expectedPnpmVersion} in .mise.toml`);
|
|
303
|
+
process.exit(1);
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
if (packageJson.packageManager !== `pnpm@${expectedPnpmVersion}`) {
|
|
307
|
+
console.error(`Generated app package must pin pnpm@${expectedPnpmVersion}`);
|
|
308
|
+
process.exit(1);
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
if (packageJson.engines?.pnpm !== `>=${expectedPnpmVersion} <11.6.0`) {
|
|
312
|
+
console.error(`Generated app package must require pnpm >=${expectedPnpmVersion} <11.6.0`);
|
|
313
|
+
process.exit(1);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (packageJson.pnpm !== undefined) {
|
|
317
|
+
console.error('Generated app must keep pnpm policy in pnpm-workspace.yaml, not package.json');
|
|
318
|
+
process.exit(1);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
if (readPnpmConfig('minimumReleaseAge') !== 1440) {
|
|
322
|
+
console.error('pnpm minimumReleaseAge must be 1440');
|
|
323
|
+
process.exit(1);
|
|
324
|
+
}
|
|
325
|
+
if (readPnpmConfig('minimumReleaseAgeStrict') !== true) {
|
|
326
|
+
console.error('pnpm minimumReleaseAgeStrict must be true');
|
|
327
|
+
process.exit(1);
|
|
328
|
+
}
|
|
329
|
+
if (readPnpmConfig('minimumReleaseAgeIgnoreMissingTime') !== false) {
|
|
330
|
+
console.error('pnpm minimumReleaseAgeIgnoreMissingTime must be false');
|
|
331
|
+
process.exit(1);
|
|
332
|
+
}
|
|
333
|
+
if (readPnpmConfig('minimumReleaseAgeExclude') !== undefined) {
|
|
334
|
+
console.error('pnpm minimumReleaseAgeExclude must stay unset');
|
|
335
|
+
process.exit(1);
|
|
336
|
+
}
|
|
337
|
+
if (readPnpmConfig('trustPolicy') !== 'no-downgrade') {
|
|
338
|
+
console.error('pnpm trustPolicy must be no-downgrade');
|
|
339
|
+
process.exit(1);
|
|
340
|
+
}
|
|
341
|
+
for (const [key, expected] of [
|
|
342
|
+
['trustPolicyIgnoreAfter', 1440],
|
|
343
|
+
['blockExoticSubdeps', true],
|
|
344
|
+
['engineStrict', true],
|
|
345
|
+
['pmOnFail', 'error'],
|
|
346
|
+
['verifyDepsBeforeRun', 'error'],
|
|
347
|
+
['strictDepBuilds', true],
|
|
348
|
+
]) {
|
|
349
|
+
if (readPnpmConfig(key) !== expected) {
|
|
350
|
+
console.error(`pnpm ${key} must be ${String(expected)}`);
|
|
351
|
+
process.exit(1);
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
if (
|
|
355
|
+
JSON.stringify(readPnpmConfig('allowBuilds')) !==
|
|
356
|
+
JSON.stringify({
|
|
357
|
+
'@swc/core': true,
|
|
358
|
+
'core-js': true,
|
|
359
|
+
esbuild: true,
|
|
360
|
+
lefthook: true,
|
|
361
|
+
'msgpackr-extract': true,
|
|
362
|
+
sharp: true,
|
|
363
|
+
workerd: true,
|
|
364
|
+
})
|
|
365
|
+
) {
|
|
366
|
+
console.error('pnpm allowBuilds must approve only the generated app build dependencies');
|
|
367
|
+
process.exit(1);
|
|
368
|
+
}
|
|
369
|
+
if (readPnpmConfig('onlyBuiltDependencies') !== undefined) {
|
|
370
|
+
console.error('pnpm onlyBuiltDependencies must not be set');
|
|
371
|
+
process.exit(1);
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
if (packageJson.modernjs?.preset !== 'presetUltramodern') {
|
|
375
|
+
console.error('package.json must declare presetUltramodern metadata');
|
|
376
|
+
process.exit(1);
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
if (
|
|
380
|
+
packageJson.modernjs?.packageSource?.config !== './.modernjs/ultramodern-package-source.json'
|
|
381
|
+
) {
|
|
382
|
+
console.error('package.json must retain package source metadata location');
|
|
383
|
+
process.exit(1);
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
if (packageSource.schemaVersion !== 1) {
|
|
387
|
+
console.error('Package source metadata must use schemaVersion 1');
|
|
388
|
+
process.exit(1);
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
if (packageSource.preset !== 'presetUltramodern') {
|
|
392
|
+
console.error('Package source metadata must declare presetUltramodern');
|
|
393
|
+
process.exit(1);
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
if (packageSource.strategy !== 'workspace' && packageSource.strategy !== 'install') {
|
|
397
|
+
console.error('Package source strategy must be workspace or install');
|
|
398
|
+
process.exit(1);
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
const expectedModernPackages = [
|
|
402
|
+
'@modern-js/runtime',
|
|
403
|
+
'@modern-js/app-tools',
|
|
404
|
+
'@modern-js/tsconfig',
|
|
405
|
+
'@modern-js/plugin-i18n',
|
|
406
|
+
'@modern-js/plugin-tanstack',
|
|
407
|
+
'@modern-js/plugin-bff',
|
|
408
|
+
'@modern-js/adapter-rstest',
|
|
409
|
+
];
|
|
410
|
+
|
|
411
|
+
for (const packageName of expectedModernPackages) {
|
|
412
|
+
if (!packageSource.modernPackages?.packages?.includes(packageName)) {
|
|
413
|
+
console.error(`Package source metadata must include ${packageName}`);
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const expectedModernSpecifier = packageSource.modernPackages?.specifier;
|
|
419
|
+
if (typeof expectedModernSpecifier !== 'string' || expectedModernSpecifier.length === 0) {
|
|
420
|
+
console.error('Package source metadata must provide a Modern package specifier');
|
|
421
|
+
process.exit(1);
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const expectedModernDependency = (packageName) => {
|
|
425
|
+
const alias = packageSource.modernPackages?.aliases?.[packageName];
|
|
426
|
+
return typeof alias === 'string'
|
|
427
|
+
? `npm:${alias}@${expectedModernSpecifier}`
|
|
428
|
+
: expectedModernSpecifier;
|
|
429
|
+
};
|
|
430
|
+
|
|
431
|
+
for (const packageName of [
|
|
432
|
+
'@modern-js/runtime',
|
|
433
|
+
'@modern-js/plugin-i18n',
|
|
434
|
+
'@modern-js/plugin-tanstack',
|
|
435
|
+
]) {
|
|
436
|
+
if (
|
|
437
|
+
packageJson.dependencies?.[packageName] &&
|
|
438
|
+
packageJson.dependencies[packageName] !== expectedModernDependency(packageName)
|
|
439
|
+
) {
|
|
440
|
+
console.error(`Dependency ${packageName} must match package source metadata`);
|
|
441
|
+
process.exit(1);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
for (const packageName of [
|
|
446
|
+
'@modern-js/app-tools',
|
|
447
|
+
'@modern-js/adapter-rstest',
|
|
448
|
+
'@modern-js/tsconfig',
|
|
449
|
+
'@modern-js/plugin-bff',
|
|
450
|
+
]) {
|
|
451
|
+
if (
|
|
452
|
+
packageJson.devDependencies?.[packageName] &&
|
|
453
|
+
packageJson.devDependencies[packageName] !== expectedModernDependency(packageName)
|
|
454
|
+
) {
|
|
455
|
+
console.error(`Dev dependency ${packageName} must match package source metadata`);
|
|
456
|
+
process.exit(1);
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
for (const dependency of ['@modern-js/plugin-i18n', 'i18next', 'react-i18next']) {
|
|
461
|
+
if (!packageJson.dependencies?.[dependency]) {
|
|
462
|
+
console.error(`Missing dependency: ${dependency}`);
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
|
|
154
467
|
for (const dependency of [
|
|
155
468
|
'@effect/tsgo',
|
|
469
|
+
'@modern-js/adapter-rstest',
|
|
470
|
+
'@rstest/core',
|
|
156
471
|
'@typescript/native-preview',
|
|
472
|
+
'happy-dom',
|
|
473
|
+
{{#if enableTailwind}}
|
|
474
|
+
'@tailwindcss/postcss',
|
|
475
|
+
'postcss',
|
|
476
|
+
'tailwindcss',
|
|
477
|
+
{{/if}}
|
|
478
|
+
{{#unless isSubproject}}
|
|
479
|
+
'lefthook',
|
|
157
480
|
'oxlint',
|
|
158
481
|
'oxfmt',
|
|
159
482
|
'ultracite',
|
|
483
|
+
{{/unless}}
|
|
160
484
|
]) {
|
|
161
485
|
if (!packageJson.devDependencies?.[dependency]) {
|
|
162
486
|
console.error(`Missing devDependency: ${dependency}`);
|
|
@@ -164,23 +488,37 @@ for (const dependency of [
|
|
|
164
488
|
}
|
|
165
489
|
}
|
|
166
490
|
|
|
491
|
+
{{#if enableTailwind}}
|
|
492
|
+
if (
|
|
493
|
+
packageJson.devDependencies?.tailwindcss !== '^4.3.0' ||
|
|
494
|
+
packageJson.devDependencies?.['@tailwindcss/postcss'] !== '^4.3.0'
|
|
495
|
+
) {
|
|
496
|
+
console.error('Tailwind CSS dependencies must use the UltraModern default baseline');
|
|
497
|
+
process.exit(1);
|
|
498
|
+
}
|
|
499
|
+
{{/if}}
|
|
500
|
+
{{#unless enableTailwind}}
|
|
501
|
+
if (
|
|
502
|
+
packageJson.devDependencies?.tailwindcss ||
|
|
503
|
+
packageJson.devDependencies?.['@tailwindcss/postcss'] ||
|
|
504
|
+
packageJson.devDependencies?.postcss
|
|
505
|
+
) {
|
|
506
|
+
console.error('Tailwind CSS dependencies must be absent when Tailwind is disabled');
|
|
507
|
+
process.exit(1);
|
|
508
|
+
}
|
|
509
|
+
{{/unless}}
|
|
510
|
+
|
|
511
|
+
{{#unless isSubproject}}
|
|
167
512
|
const privateSource = skillsLock.sources?.find(
|
|
168
|
-
source => source.repository === 'https://github.com/TechsioCZ/skills',
|
|
513
|
+
(source) => source.repository === 'https://github.com/TechsioCZ/skills',
|
|
169
514
|
);
|
|
170
|
-
const privateSkills = new Set(
|
|
171
|
-
|
|
172
|
-
);
|
|
173
|
-
for (const skillName of [
|
|
174
|
-
'plan-graph',
|
|
175
|
-
'dag',
|
|
176
|
-
'subagent-graph',
|
|
177
|
-
'helm',
|
|
178
|
-
'debugger-mode',
|
|
179
|
-
]) {
|
|
515
|
+
const privateSkills = new Set(privateSource?.baseline?.map((skill) => skill.name));
|
|
516
|
+
for (const skillName of ['plan-graph', 'dag', 'subagent-graph', 'helm', 'debugger-mode']) {
|
|
180
517
|
if (!privateSkills.has(skillName)) {
|
|
181
518
|
console.error(`Missing private skill allowlist entry: ${skillName}`);
|
|
182
519
|
process.exit(1);
|
|
183
520
|
}
|
|
184
521
|
}
|
|
522
|
+
{{/unless}}
|
|
185
523
|
|
|
186
524
|
console.log('Ultramodern contract check passed.');
|
|
@@ -1,9 +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({
|
|
4
|
-
{
|
|
5
|
-
|
|
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
|
+
},
|
|
19
|
+
{{#if isTanstackRouter}} router: {
|
|
6
20
|
framework: 'tanstack',
|
|
7
21
|
},
|
|
8
|
-
{{/if}}
|
|
22
|
+
{{/if~}}
|
|
9
23
|
});
|