@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.
- package/dist/cjs/ultramodern-workspace/add-vertical.cjs +18 -0
- package/dist/cjs/ultramodern-workspace/contracts.cjs +5 -3
- package/dist/cjs/ultramodern-workspace/demo-components.cjs +9 -18
- package/dist/cjs/ultramodern-workspace/descriptors.cjs +2 -2
- package/dist/cjs/ultramodern-workspace/module-federation.cjs +10 -1
- package/dist/cjs/ultramodern-workspace/package-json.cjs +74 -13
- package/dist/cjs/ultramodern-workspace/versions.cjs +1 -5
- package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +7 -0
- package/dist/cjs/ultramodern-workspace/write-workspace.cjs +3 -7
- package/dist/esm/ultramodern-workspace/add-vertical.js +19 -1
- package/dist/esm/ultramodern-workspace/contracts.js +6 -4
- package/dist/esm/ultramodern-workspace/demo-components.js +9 -18
- package/dist/esm/ultramodern-workspace/descriptors.js +2 -2
- package/dist/esm/ultramodern-workspace/module-federation.js +10 -1
- package/dist/esm/ultramodern-workspace/package-json.js +62 -10
- package/dist/esm/ultramodern-workspace/versions.js +2 -3
- package/dist/esm/ultramodern-workspace/workspace-scripts.js +5 -1
- package/dist/esm/ultramodern-workspace/write-workspace.js +5 -9
- package/dist/esm-node/ultramodern-workspace/add-vertical.js +19 -1
- package/dist/esm-node/ultramodern-workspace/contracts.js +6 -4
- package/dist/esm-node/ultramodern-workspace/demo-components.js +9 -18
- package/dist/esm-node/ultramodern-workspace/descriptors.js +2 -2
- package/dist/esm-node/ultramodern-workspace/module-federation.js +10 -1
- package/dist/esm-node/ultramodern-workspace/package-json.js +62 -10
- package/dist/esm-node/ultramodern-workspace/versions.js +2 -3
- package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +5 -1
- package/dist/esm-node/ultramodern-workspace/write-workspace.js +5 -9
- package/dist/types/ultramodern-workspace/package-json.d.ts +11 -2
- package/dist/types/ultramodern-workspace/versions.d.ts +1 -2
- package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +1 -0
- package/package.json +4 -4
- package/template-workspace/AGENTS.md.handlebars +2 -1
- package/template-workspace/README.md.handlebars +7 -0
- package/template-workspace/oxfmt.config.ts +3 -1
- package/template-workspace/oxlint.config.ts +3 -1
- package/template-workspace/pnpm-workspace.yaml.handlebars +8 -0
- package/template-workspace/scripts/bootstrap-agent-skills.mjs +42 -10
- package/templates/app/shell-frame.tsx +1 -2
- package/templates/app/ultramodern-route-head.tsx.handlebars +23 -12
- package/templates/packages/shared-contracts-index.ts +206 -272
- package/templates/workspace-scripts/ultramodern-typecheck.mjs +197 -0
- 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
|
|
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
|
-
|
|
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`);
|