@bleedingdev/modern-js-create 3.2.0-ultramodern.9 → 3.2.0-ultramodern.91

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