@bleedingdev/modern-js-create 3.4.0-ultramodern.1 → 3.4.0-ultramodern.3

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 (42) hide show
  1. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +18 -0
  2. package/dist/cjs/ultramodern-workspace/contracts.cjs +5 -3
  3. package/dist/cjs/ultramodern-workspace/demo-components.cjs +9 -18
  4. package/dist/cjs/ultramodern-workspace/descriptors.cjs +2 -2
  5. package/dist/cjs/ultramodern-workspace/module-federation.cjs +10 -1
  6. package/dist/cjs/ultramodern-workspace/package-json.cjs +74 -13
  7. package/dist/cjs/ultramodern-workspace/versions.cjs +1 -5
  8. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +7 -0
  9. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +3 -7
  10. package/dist/esm/ultramodern-workspace/add-vertical.js +19 -1
  11. package/dist/esm/ultramodern-workspace/contracts.js +6 -4
  12. package/dist/esm/ultramodern-workspace/demo-components.js +9 -18
  13. package/dist/esm/ultramodern-workspace/descriptors.js +2 -2
  14. package/dist/esm/ultramodern-workspace/module-federation.js +10 -1
  15. package/dist/esm/ultramodern-workspace/package-json.js +62 -10
  16. package/dist/esm/ultramodern-workspace/versions.js +2 -3
  17. package/dist/esm/ultramodern-workspace/workspace-scripts.js +5 -1
  18. package/dist/esm/ultramodern-workspace/write-workspace.js +5 -9
  19. package/dist/esm-node/ultramodern-workspace/add-vertical.js +19 -1
  20. package/dist/esm-node/ultramodern-workspace/contracts.js +6 -4
  21. package/dist/esm-node/ultramodern-workspace/demo-components.js +9 -18
  22. package/dist/esm-node/ultramodern-workspace/descriptors.js +2 -2
  23. package/dist/esm-node/ultramodern-workspace/module-federation.js +10 -1
  24. package/dist/esm-node/ultramodern-workspace/package-json.js +62 -10
  25. package/dist/esm-node/ultramodern-workspace/versions.js +2 -3
  26. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +5 -1
  27. package/dist/esm-node/ultramodern-workspace/write-workspace.js +5 -9
  28. package/dist/types/ultramodern-workspace/package-json.d.ts +11 -2
  29. package/dist/types/ultramodern-workspace/versions.d.ts +1 -2
  30. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +1 -0
  31. package/package.json +4 -4
  32. package/template-workspace/AGENTS.md.handlebars +2 -1
  33. package/template-workspace/README.md.handlebars +7 -0
  34. package/template-workspace/oxfmt.config.ts +3 -1
  35. package/template-workspace/oxlint.config.ts +3 -1
  36. package/template-workspace/pnpm-workspace.yaml.handlebars +8 -0
  37. package/template-workspace/scripts/bootstrap-agent-skills.mjs +42 -10
  38. package/templates/app/shell-frame.tsx +1 -2
  39. package/templates/app/ultramodern-route-head.tsx.handlebars +23 -12
  40. package/templates/packages/shared-contracts-index.ts +206 -272
  41. package/templates/workspace-scripts/ultramodern-typecheck.mjs +197 -0
  42. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +166 -2
@@ -0,0 +1,197 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawnSync } from 'node:child_process';
4
+ import {
5
+ accessSync,
6
+ chmodSync,
7
+ constants,
8
+ existsSync,
9
+ mkdirSync,
10
+ } from 'node:fs';
11
+ import os from 'node:os';
12
+ import { dirname, join } from 'node:path';
13
+ import { fileURLToPath } from 'node:url';
14
+
15
+ const args = process.argv.slice(2);
16
+ const workspaceRoot = join(dirname(fileURLToPath(import.meta.url)), '..');
17
+ const cpuCount = Math.max(
18
+ 1,
19
+ typeof os.availableParallelism === 'function'
20
+ ? os.availableParallelism()
21
+ : os.cpus().length,
22
+ );
23
+
24
+ function fail(message) {
25
+ console.error(message);
26
+ process.exit(1);
27
+ }
28
+
29
+ function parsePositiveInt(value, label) {
30
+ const parsed = Number(value);
31
+ if (!Number.isInteger(parsed) || parsed < 1) {
32
+ fail(`${label} must be a positive integer.`);
33
+ }
34
+ return parsed;
35
+ }
36
+
37
+ function envPositiveInt(name, fallback) {
38
+ const value = process.env[name]?.trim();
39
+ return value ? parsePositiveInt(value, name) : fallback;
40
+ }
41
+
42
+ function readArgs() {
43
+ let buildTarget;
44
+ let projectTarget;
45
+ let checkers;
46
+ let builders;
47
+ const passthrough = [];
48
+
49
+ for (let index = 0; index < args.length; index += 1) {
50
+ const arg = args[index];
51
+ const next = args[index + 1];
52
+
53
+ if (arg === '--') {
54
+ passthrough.push(...args.slice(index + 1));
55
+ break;
56
+ }
57
+
58
+ if (arg === '--build' || arg === '-b') {
59
+ buildTarget = next && !next.startsWith('-') ? next : 'tsconfig.json';
60
+ if (buildTarget === next) {
61
+ index += 1;
62
+ }
63
+ continue;
64
+ }
65
+
66
+ if (arg === '--project' || arg === '-p') {
67
+ if (!next || next.startsWith('-')) {
68
+ fail(`${arg} requires a tsconfig path.`);
69
+ }
70
+ projectTarget = next;
71
+ index += 1;
72
+ continue;
73
+ }
74
+
75
+ if (arg === '--checkers') {
76
+ if (!next || next.startsWith('-')) {
77
+ fail('--checkers requires a positive integer.');
78
+ }
79
+ checkers = parsePositiveInt(next, '--checkers');
80
+ index += 1;
81
+ continue;
82
+ }
83
+
84
+ if (arg === '--builders') {
85
+ if (!next || next.startsWith('-')) {
86
+ fail('--builders requires a positive integer.');
87
+ }
88
+ builders = parsePositiveInt(next, '--builders');
89
+ index += 1;
90
+ continue;
91
+ }
92
+
93
+ passthrough.push(arg);
94
+ }
95
+
96
+ if (buildTarget && projectTarget) {
97
+ fail('Choose either --build or --project, not both.');
98
+ }
99
+
100
+ return {
101
+ mode: buildTarget ? 'build' : 'project',
102
+ target: buildTarget ?? projectTarget ?? 'tsconfig.json',
103
+ checkers,
104
+ builders,
105
+ passthrough,
106
+ };
107
+ }
108
+
109
+ function resolveTsgoBinary() {
110
+ const explicitBinary = process.env.EFFECT_TSGO_BIN || process.env.TSGO_BIN;
111
+ if (explicitBinary) {
112
+ return explicitBinary;
113
+ }
114
+
115
+ const cli = process.env.EFFECT_TSGO_CLI || 'effect-tsgo';
116
+ const result = spawnSync(cli, ['get-exe-path'], {
117
+ encoding: 'utf8',
118
+ stdio: ['ignore', 'pipe', 'pipe'],
119
+ });
120
+
121
+ if (result.error) {
122
+ fail(
123
+ `Unable to run ${cli}. Run pnpm install or set EFFECT_TSGO_BIN to the native-preview tsgo binary.`,
124
+ );
125
+ }
126
+ if (result.status !== 0) {
127
+ fail(
128
+ result.stderr?.trim() ||
129
+ `${cli} get-exe-path exited with status ${result.status}.`,
130
+ );
131
+ }
132
+
133
+ return result.stdout.trim() || cli;
134
+ }
135
+
136
+ const parsed = readArgs();
137
+ const defaultBuilders = Math.min(8, Math.max(1, Math.floor(cpuCount / 2)));
138
+ const builders =
139
+ parsed.builders ??
140
+ envPositiveInt('ULTRAMODERN_TSGO_BUILDERS', defaultBuilders);
141
+ const defaultCheckers =
142
+ parsed.mode === 'build'
143
+ ? Math.min(4, Math.max(1, Math.floor(cpuCount / builders)))
144
+ : Math.min(8, Math.max(2, cpuCount - 1));
145
+ const checkers =
146
+ parsed.checkers ??
147
+ envPositiveInt('ULTRAMODERN_TSGO_CHECKERS', defaultCheckers);
148
+ const tsgoBin = resolveTsgoBinary();
149
+
150
+ if (!existsSync(tsgoBin)) {
151
+ fail(`TS-Go binary not found at ${tsgoBin}. Run pnpm install.`);
152
+ }
153
+
154
+ try {
155
+ accessSync(tsgoBin, constants.X_OK);
156
+ } catch {
157
+ chmodSync(tsgoBin, 0o755);
158
+ }
159
+
160
+ mkdirSync(join(workspaceRoot, 'node_modules/.cache/tsgo'), {
161
+ recursive: true,
162
+ });
163
+
164
+ const tsgoArgs =
165
+ parsed.mode === 'build'
166
+ ? [
167
+ '--build',
168
+ parsed.target,
169
+ '--pretty',
170
+ 'false',
171
+ '--checkers',
172
+ String(checkers),
173
+ '--builders',
174
+ String(builders),
175
+ '--stopBuildOnErrors',
176
+ ...parsed.passthrough,
177
+ ]
178
+ : [
179
+ '--project',
180
+ parsed.target,
181
+ '--noEmit',
182
+ '--pretty',
183
+ 'false',
184
+ '--checkers',
185
+ String(checkers),
186
+ ...parsed.passthrough,
187
+ ];
188
+
189
+ const result = spawnSync(tsgoBin, tsgoArgs, {
190
+ stdio: 'inherit',
191
+ });
192
+
193
+ if (result.error) {
194
+ throw result.error;
195
+ }
196
+
197
+ process.exit(result.status ?? 1);
@@ -91,6 +91,7 @@ const expectedRemoteSubsetsForRefs = refs =>
91
91
  .map(expectedRemoteContractSubset);
92
92
  const requiredMicroVerticalPaths = vertical => [
93
93
  `${vertical.path}/package.json`,
94
+ `${vertical.path}/tsconfig.json`,
94
95
  `${vertical.path}/modern.config.ts`,
95
96
  `${vertical.path}/module-federation.config.ts`,
96
97
  `${vertical.path}/api/effect/index.ts`,
@@ -222,6 +223,15 @@ const assertMicroVerticalContractGraph = ({
222
223
  '.modernjs/ultramodern-generated-contract.json shell moduleFederation.remotes',
223
224
  'regenerate the generated shell Module Federation contract',
224
225
  );
226
+ assertSameJson(
227
+ shellContract.ssr,
228
+ {
229
+ mode: 'string',
230
+ moduleFederationAppSSR: true,
231
+ },
232
+ '.modernjs/ultramodern-generated-contract.json shell SSR contract',
233
+ 'restore generated string SSR Module Federation settings',
234
+ );
225
235
 
226
236
  for (const vertical of fullStackVerticals) {
227
237
  const expectedRefs = vertical.verticalRefs ?? [];
@@ -353,6 +363,7 @@ const assertMicroVerticalContractGraph = ({
353
363
  contract: contractEntry.effect?.contract,
354
364
  client: contractEntry.effect?.client,
355
365
  },
366
+ ssr: contractEntry.ssr,
356
367
  },
357
368
  {
358
369
  kind: 'vertical',
@@ -369,12 +380,77 @@ const assertMicroVerticalContractGraph = ({
369
380
  contract: './shared/effect/api',
370
381
  client: './effect/client',
371
382
  },
383
+ ssr: {
384
+ mode: 'string',
385
+ moduleFederationAppSSR: true,
386
+ },
372
387
  },
373
388
  `.modernjs/ultramodern-generated-contract.json apps.${vertical.id}`,
374
389
  'regenerate the generated MicroVertical contract entry',
375
390
  );
376
391
  }
377
392
  };
393
+ const toPosixPath = value => value.split(path.sep).join('/');
394
+ const referenceFrom = (fromPath, toPath) => ({
395
+ path: toPosixPath(path.relative(fromPath, toPath)),
396
+ });
397
+ const sharedPackagePaths = [
398
+ 'packages/shared-contracts',
399
+ 'packages/shared-design-tokens',
400
+ ];
401
+ const assertTsConfigReferenceGraph = () => {
402
+ const rootTsConfig = readJson('tsconfig.json');
403
+ const shellTsConfig = readJson('apps/shell-super-app/tsconfig.json');
404
+ const expectedRootReferences = [
405
+ ...sharedPackagePaths,
406
+ 'apps/shell-super-app',
407
+ ...fullStackVerticals.map(vertical => vertical.path),
408
+ ].map(referencePath => ({ path: referencePath }));
409
+ const expectedShellReferences = [
410
+ ...sharedPackagePaths,
411
+ ...fullStackVerticals.map(vertical => vertical.path),
412
+ ].map(referencePath =>
413
+ referenceFrom('apps/shell-super-app', referencePath),
414
+ );
415
+
416
+ assertSameJson(
417
+ rootTsConfig.files,
418
+ [],
419
+ 'tsconfig.json files',
420
+ 'restore the generated root project-reference tsconfig',
421
+ );
422
+ assertSameJson(
423
+ rootTsConfig.references ?? [],
424
+ expectedRootReferences,
425
+ 'tsconfig.json references',
426
+ 'restore the generated root project-reference graph',
427
+ );
428
+ assertSameJson(
429
+ shellTsConfig.references ?? [],
430
+ expectedShellReferences,
431
+ 'apps/shell-super-app/tsconfig.json references',
432
+ 'restore the generated shell project-reference graph',
433
+ );
434
+
435
+ for (const vertical of fullStackVerticals) {
436
+ const verticalTsConfig = readJson(`${vertical.path}/tsconfig.json`);
437
+ const expectedVerticalReferences = [
438
+ ...sharedPackagePaths,
439
+ ...(vertical.verticalRefs ?? [])
440
+ .map(verticalRef =>
441
+ fullStackVerticals.find(candidate => candidate.id === verticalRef),
442
+ )
443
+ .filter(Boolean)
444
+ .map(referencedVertical => referencedVertical.path),
445
+ ].map(referencePath => referenceFrom(vertical.path, referencePath));
446
+ assertSameJson(
447
+ verticalTsConfig.references ?? [],
448
+ expectedVerticalReferences,
449
+ `${vertical.path}/tsconfig.json references`,
450
+ 'restore the generated MicroVertical project-reference graph',
451
+ );
452
+ }
453
+ };
378
454
  const packageJsonFiles = startDir => {
379
455
  const files = [];
380
456
  const queue = [startDir];
@@ -511,10 +587,79 @@ const assertCloudflareQualityGates = (appId, qualityGates) => {
511
587
  assert(qualityGates?.csp?.finalMode === 'report-only-dogfood', `${appId} CSP final mode decision is missing`);
512
588
  };
513
589
  const extractAssetPrefixExpression = modernConfig => {
514
- const match = /const assetPrefix =\n(?<expression>[\s\S]*?);/u.exec(modernConfig);
590
+ const match = /const\s+assetPrefix\s*=\s*(?<expression>[\s\S]*?);/u.exec(modernConfig);
515
591
  assert(match?.groups?.expression, 'modern.config.ts must assign assetPrefix');
516
592
  return match.groups.expression;
517
593
  };
594
+ const stripYamlInlineComment = value => {
595
+ let quote = undefined;
596
+ for (let index = 0; index < value.length; index += 1) {
597
+ const character = value[index];
598
+ if (quote) {
599
+ if (character === quote && value[index - 1] !== '\\') {
600
+ quote = undefined;
601
+ }
602
+ continue;
603
+ }
604
+ if (character === '"' || character === "'") {
605
+ quote = character;
606
+ continue;
607
+ }
608
+ if (character === '#' && (index === 0 || /\s/u.test(value[index - 1]))) {
609
+ return value.slice(0, index).trimEnd();
610
+ }
611
+ }
612
+ return value.trimEnd();
613
+ };
614
+ const normalizeYamlScalar = value => {
615
+ const trimmed = stripYamlInlineComment(value).trim();
616
+ if (
617
+ ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
618
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) &&
619
+ trimmed.length >= 2
620
+ ) {
621
+ return trimmed.slice(1, -1);
622
+ }
623
+ return trimmed;
624
+ };
625
+ const extractWorkflowNodeVersions = workflowText => {
626
+ const versions = [];
627
+ const lines = workflowText.split(/\r?\n/u);
628
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
629
+ const match = /^(?<indent>\s*)node-version\s*:\s*(?<value>.*)$/u.exec(
630
+ lines[lineIndex],
631
+ );
632
+ if (!match?.groups) {
633
+ continue;
634
+ }
635
+
636
+ let value = normalizeYamlScalar(match.groups.value);
637
+ if (value === '' || value === '|' || value === '>') {
638
+ const currentIndent = match.groups.indent.length;
639
+ for (
640
+ let nextLineIndex = lineIndex + 1;
641
+ nextLineIndex < lines.length;
642
+ nextLineIndex += 1
643
+ ) {
644
+ const nextLine = lines[nextLineIndex];
645
+ const nextTrimmed = nextLine.trim();
646
+ if (nextTrimmed === '' || nextTrimmed.startsWith('#')) {
647
+ continue;
648
+ }
649
+ const nextIndent = nextLine.match(/^\s*/u)?.[0]?.length ?? 0;
650
+ if (nextIndent <= currentIndent) {
651
+ break;
652
+ }
653
+ value = normalizeYamlScalar(nextTrimmed);
654
+ break;
655
+ }
656
+ }
657
+ if (value !== '') {
658
+ versions.push(value);
659
+ }
660
+ }
661
+ return versions;
662
+ };
518
663
  const expectedWorkerName = packageSuffix => `${packageScope}-${packageSuffix}`.slice(0, 63);
519
664
  const expectedChunkLoadingGlobal = mfName =>
520
665
  `__ULTRAMODERN_${mfName
@@ -559,6 +704,7 @@ const requiredPaths = [
559
704
  '.gitignore',
560
705
  'package.json',
561
706
  'pnpm-workspace.yaml',
707
+ 'tsconfig.json',
562
708
  'tsconfig.base.json',
563
709
  'oxlint.config.ts',
564
710
  'oxfmt.config.ts',
@@ -581,7 +727,9 @@ const requiredPaths = [
581
727
  'scripts/setup-agent-reference-repos.mjs',
582
728
  'scripts/ultramodern-performance-readiness.config.mjs',
583
729
  'scripts/ultramodern-performance-readiness.mjs',
730
+ 'scripts/ultramodern-typecheck.mjs',
584
731
  'apps/shell-super-app/package.json',
732
+ 'apps/shell-super-app/tsconfig.json',
585
733
  'apps/shell-super-app/modern.config.ts',
586
734
  'apps/shell-super-app/module-federation.config.ts',
587
735
  'apps/shell-super-app/src/modern-app-env.d.ts',
@@ -597,9 +745,13 @@ const requiredPaths = [
597
745
  'apps/shell-super-app/src/routes/ultramodern-route-metadata.ts',
598
746
  'apps/shell-super-app/src/routes/[lang]/page.tsx',
599
747
  ...{{shellRouteMetaPathsJson}},
748
+ 'packages/shared-contracts/package.json',
600
749
  'packages/shared-contracts/src/index.ts',
750
+ 'packages/shared-contracts/tsconfig.json',
751
+ 'packages/shared-design-tokens/package.json',
601
752
  'packages/shared-design-tokens/src/index.ts',
602
753
  'packages/shared-design-tokens/src/tokens.css',
754
+ 'packages/shared-design-tokens/tsconfig.json',
603
755
  ];
604
756
 
605
757
  for (const vertical of fullStackVerticals) {
@@ -643,6 +795,7 @@ assertMicroVerticalContractGraph({
643
795
  overlay,
644
796
  shellPackage,
645
797
  });
798
+ assertTsConfigReferenceGraph();
646
799
 
647
800
  assert(rootPackage.private === true, 'Root package must be private');
648
801
  assert(typeof rootPackage.packageManager === 'string', 'Root must declare packageManager');
@@ -660,7 +813,12 @@ assert(generatedContract.node?.engineRange === '>=26', 'Generated contract must
660
813
  assert(readText('.mise.toml').includes(`node = "${expectedNodeVersion}"`), 'mise must pin the generated Node version');
661
814
  assert(readText('.mise.toml').includes(`pnpm = "${packageManagerPnpmVersion}"`), 'mise must pin the generated pnpm version');
662
815
  const workflowText = readText('.github/workflows/ultramodern-workspace-gates.yml');
663
- assert(workflowText.includes(`node-version: "${expectedNodeVersion}"`), 'CI workflow must pin the generated Node version');
816
+ const workflowNodeVersions = extractWorkflowNodeVersions(workflowText);
817
+ assert(workflowNodeVersions.length > 0, 'CI workflow must configure setup-node node-version');
818
+ assert(
819
+ workflowNodeVersions.every(nodeVersion => nodeVersion === expectedNodeVersion),
820
+ `CI workflow must pin the generated Node version ${expectedNodeVersion}; found ${workflowNodeVersions.join(', ')}`,
821
+ );
664
822
  assert(!workflowText.includes('FORCE_JAVASCRIPT_ACTIONS_TO_NODE24'), 'CI workflow must not carry the legacy Node 24 override');
665
823
  assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare presetUltramodern');
666
824
  assert(rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json', 'Root must point at package source metadata');
@@ -711,6 +869,7 @@ assert(
711
869
  );
712
870
  assert(rootPackage.scripts?.['cloudflare:build'] === expectedCloudflareBuildScript, 'Root cloudflare:build script is incorrect');
713
871
  assert(!('ultramodern:check' in (rootPackage.scripts ?? {})), 'Root must not expose ultramodern:check');
872
+ assert(rootPackage.scripts?.typecheck === 'node ./scripts/ultramodern-typecheck.mjs --build tsconfig.json', 'Root typecheck must run TS-Go build mode across project references');
714
873
  assert(rootPackage.scripts?.['contract:check'] === 'node ./scripts/validate-ultramodern-workspace.mjs', 'Root must expose contract:check');
715
874
  assert(rootPackage.scripts?.['i18n:boundaries'] === 'node ./scripts/check-ultramodern-i18n-boundaries.mjs', 'Root must expose i18n:boundaries');
716
875
  assert(rootPackage.scripts?.['performance:readiness'] === 'node ./scripts/ultramodern-performance-readiness.mjs', 'Root must expose default-on performance readiness diagnostics');
@@ -723,6 +882,9 @@ assert(performanceReadinessConfig.includes("failOn: 'framework-invariant'"), 'Pe
723
882
  assert(performanceReadinessScript.includes("ULTRAMODERN_PERFORMANCE_READINESS_DIAGNOSTICS"), 'Performance readiness script must support env opt-out');
724
883
  assert(performanceReadinessScript.includes('ultramodern-performance-readiness-diagnostics-v1'), 'Performance readiness script must emit the deterministic report profile');
725
884
  const i18nBoundaryScript = readText('scripts/check-ultramodern-i18n-boundaries.mjs');
885
+ const ultramodernTypecheckScript = readText('scripts/ultramodern-typecheck.mjs');
886
+ assert(ultramodernTypecheckScript.includes("'--checkers'") && ultramodernTypecheckScript.includes("'--builders'"), 'Root typecheck script must drive TS-Go checker and builder parallelism');
887
+ assert(ultramodernTypecheckScript.includes('effect-tsgo') && ultramodernTypecheckScript.includes('get-exe-path'), 'Root typecheck script must resolve the @effect/tsgo native-preview binary');
726
888
  assert(
727
889
  i18nBoundaryScript.includes("from '@modern-js/code-tools'") &&
728
890
  i18nBoundaryScript.includes('runWorkspaceSourceCheck'),
@@ -819,6 +981,7 @@ assert(shellModernConfig.includes('assetPrefix,'), 'Shell modern.config.ts must
819
981
  assert(shellContract?.config?.dev?.assetPrefix === '/', 'Shell dev asset prefix must stay origin-relative');
820
982
  assert(shellContract?.config?.output?.assetPrefix?.default === '/', 'Shell asset prefix must default to origin-relative paths');
821
983
  assert(JSON.stringify(shellContract?.config?.output?.assetPrefix?.envFallbackOrder) === JSON.stringify(['MODERN_ASSET_PREFIX', 'ULTRAMODERN_ASSET_PREFIX']), 'Shell asset prefix env fallback order is incorrect');
984
+ assert(shellContract?.config?.output?.disableTsChecker === false, 'Shell must keep the framework TypeScript checker enabled');
822
985
  assert(shellContract?.config?.performance?.readinessDiagnostics?.default === 'enabled', 'Shell performance readiness diagnostics must be default-on');
823
986
  assert(shellContract?.config?.performance?.readinessDiagnostics?.failOn === 'framework-invariant', 'Shell performance readiness diagnostics must only fail framework invariants by default');
824
987
  assert(shellContract?.config?.performance?.readinessDiagnostics?.optOut?.env === 'ULTRAMODERN_PERFORMANCE_READINESS_DIAGNOSTICS=false', 'Shell performance readiness env opt-out is incorrect');
@@ -913,6 +1076,7 @@ for (const vertical of fullStackVerticals) {
913
1076
  assert(contractEntry?.config?.dev?.assetPrefix === '/', `${vertical.id} dev asset prefix must stay origin-relative`);
914
1077
  assert(contractEntry?.config?.output?.assetPrefix?.default === '/', `${vertical.id} asset prefix must default to origin-relative paths`);
915
1078
  assert(JSON.stringify(contractEntry?.config?.output?.assetPrefix?.envFallbackOrder) === JSON.stringify(['MODERN_ASSET_PREFIX', 'ULTRAMODERN_ASSET_PREFIX']), `${vertical.id} asset prefix env fallback order is incorrect`);
1079
+ assert(contractEntry?.config?.output?.disableTsChecker === false, `${vertical.id} must keep the framework TypeScript checker enabled`);
916
1080
  assert(contractEntry?.config?.performance?.readinessDiagnostics?.default === 'enabled', `${vertical.id} performance readiness diagnostics must be default-on`);
917
1081
  assert(contractEntry?.config?.performance?.readinessDiagnostics?.failOn === 'framework-invariant', `${vertical.id} performance readiness diagnostics must only fail framework invariants by default`);
918
1082
  assert(contractEntry?.config?.performance?.readinessDiagnostics?.optOut?.config === 'scripts/ultramodern-performance-readiness.config.mjs', `${vertical.id} performance readiness opt-out config is incorrect`);