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

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 (37) hide show
  1. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +18 -0
  2. package/dist/cjs/ultramodern-workspace/contracts.cjs +3 -3
  3. package/dist/cjs/ultramodern-workspace/descriptors.cjs +2 -2
  4. package/dist/cjs/ultramodern-workspace/module-federation.cjs +10 -1
  5. package/dist/cjs/ultramodern-workspace/package-json.cjs +74 -13
  6. package/dist/cjs/ultramodern-workspace/versions.cjs +1 -5
  7. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +7 -0
  8. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +3 -7
  9. package/dist/esm/ultramodern-workspace/add-vertical.js +19 -1
  10. package/dist/esm/ultramodern-workspace/contracts.js +4 -4
  11. package/dist/esm/ultramodern-workspace/descriptors.js +2 -2
  12. package/dist/esm/ultramodern-workspace/module-federation.js +10 -1
  13. package/dist/esm/ultramodern-workspace/package-json.js +62 -10
  14. package/dist/esm/ultramodern-workspace/versions.js +2 -3
  15. package/dist/esm/ultramodern-workspace/workspace-scripts.js +5 -1
  16. package/dist/esm/ultramodern-workspace/write-workspace.js +5 -9
  17. package/dist/esm-node/ultramodern-workspace/add-vertical.js +19 -1
  18. package/dist/esm-node/ultramodern-workspace/contracts.js +4 -4
  19. package/dist/esm-node/ultramodern-workspace/descriptors.js +2 -2
  20. package/dist/esm-node/ultramodern-workspace/module-federation.js +10 -1
  21. package/dist/esm-node/ultramodern-workspace/package-json.js +62 -10
  22. package/dist/esm-node/ultramodern-workspace/versions.js +2 -3
  23. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +5 -1
  24. package/dist/esm-node/ultramodern-workspace/write-workspace.js +5 -9
  25. package/dist/types/ultramodern-workspace/package-json.d.ts +11 -2
  26. package/dist/types/ultramodern-workspace/versions.d.ts +1 -2
  27. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +1 -0
  28. package/package.json +4 -4
  29. package/template-workspace/AGENTS.md.handlebars +2 -1
  30. package/template-workspace/README.md.handlebars +7 -0
  31. package/template-workspace/oxfmt.config.ts +3 -1
  32. package/template-workspace/oxlint.config.ts +3 -1
  33. package/template-workspace/scripts/bootstrap-agent-skills.mjs +23 -8
  34. package/templates/app/ultramodern-route-head.tsx.handlebars +20 -8
  35. package/templates/packages/shared-contracts-index.ts +206 -272
  36. package/templates/workspace-scripts/ultramodern-typecheck.mjs +197 -0
  37. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +104 -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,6 +380,10 @@ 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',
@@ -511,10 +526,79 @@ const assertCloudflareQualityGates = (appId, qualityGates) => {
511
526
  assert(qualityGates?.csp?.finalMode === 'report-only-dogfood', `${appId} CSP final mode decision is missing`);
512
527
  };
513
528
  const extractAssetPrefixExpression = modernConfig => {
514
- const match = /const assetPrefix =\n(?<expression>[\s\S]*?);/u.exec(modernConfig);
529
+ const match = /const\s+assetPrefix\s*=\s*(?<expression>[\s\S]*?);/u.exec(modernConfig);
515
530
  assert(match?.groups?.expression, 'modern.config.ts must assign assetPrefix');
516
531
  return match.groups.expression;
517
532
  };
533
+ const stripYamlInlineComment = value => {
534
+ let quote = undefined;
535
+ for (let index = 0; index < value.length; index += 1) {
536
+ const character = value[index];
537
+ if (quote) {
538
+ if (character === quote && value[index - 1] !== '\\') {
539
+ quote = undefined;
540
+ }
541
+ continue;
542
+ }
543
+ if (character === '"' || character === "'") {
544
+ quote = character;
545
+ continue;
546
+ }
547
+ if (character === '#' && (index === 0 || /\s/u.test(value[index - 1]))) {
548
+ return value.slice(0, index).trimEnd();
549
+ }
550
+ }
551
+ return value.trimEnd();
552
+ };
553
+ const normalizeYamlScalar = value => {
554
+ const trimmed = stripYamlInlineComment(value).trim();
555
+ if (
556
+ ((trimmed.startsWith('"') && trimmed.endsWith('"')) ||
557
+ (trimmed.startsWith("'") && trimmed.endsWith("'"))) &&
558
+ trimmed.length >= 2
559
+ ) {
560
+ return trimmed.slice(1, -1);
561
+ }
562
+ return trimmed;
563
+ };
564
+ const extractWorkflowNodeVersions = workflowText => {
565
+ const versions = [];
566
+ const lines = workflowText.split(/\r?\n/u);
567
+ for (let lineIndex = 0; lineIndex < lines.length; lineIndex += 1) {
568
+ const match = /^(?<indent>\s*)node-version\s*:\s*(?<value>.*)$/u.exec(
569
+ lines[lineIndex],
570
+ );
571
+ if (!match?.groups) {
572
+ continue;
573
+ }
574
+
575
+ let value = normalizeYamlScalar(match.groups.value);
576
+ if (value === '' || value === '|' || value === '>') {
577
+ const currentIndent = match.groups.indent.length;
578
+ for (
579
+ let nextLineIndex = lineIndex + 1;
580
+ nextLineIndex < lines.length;
581
+ nextLineIndex += 1
582
+ ) {
583
+ const nextLine = lines[nextLineIndex];
584
+ const nextTrimmed = nextLine.trim();
585
+ if (nextTrimmed === '' || nextTrimmed.startsWith('#')) {
586
+ continue;
587
+ }
588
+ const nextIndent = nextLine.match(/^\s*/u)?.[0]?.length ?? 0;
589
+ if (nextIndent <= currentIndent) {
590
+ break;
591
+ }
592
+ value = normalizeYamlScalar(nextTrimmed);
593
+ break;
594
+ }
595
+ }
596
+ if (value !== '') {
597
+ versions.push(value);
598
+ }
599
+ }
600
+ return versions;
601
+ };
518
602
  const expectedWorkerName = packageSuffix => `${packageScope}-${packageSuffix}`.slice(0, 63);
519
603
  const expectedChunkLoadingGlobal = mfName =>
520
604
  `__ULTRAMODERN_${mfName
@@ -559,6 +643,7 @@ const requiredPaths = [
559
643
  '.gitignore',
560
644
  'package.json',
561
645
  'pnpm-workspace.yaml',
646
+ 'tsconfig.json',
562
647
  'tsconfig.base.json',
563
648
  'oxlint.config.ts',
564
649
  'oxfmt.config.ts',
@@ -581,7 +666,9 @@ const requiredPaths = [
581
666
  'scripts/setup-agent-reference-repos.mjs',
582
667
  'scripts/ultramodern-performance-readiness.config.mjs',
583
668
  'scripts/ultramodern-performance-readiness.mjs',
669
+ 'scripts/ultramodern-typecheck.mjs',
584
670
  'apps/shell-super-app/package.json',
671
+ 'apps/shell-super-app/tsconfig.json',
585
672
  'apps/shell-super-app/modern.config.ts',
586
673
  'apps/shell-super-app/module-federation.config.ts',
587
674
  'apps/shell-super-app/src/modern-app-env.d.ts',
@@ -597,9 +684,13 @@ const requiredPaths = [
597
684
  'apps/shell-super-app/src/routes/ultramodern-route-metadata.ts',
598
685
  'apps/shell-super-app/src/routes/[lang]/page.tsx',
599
686
  ...{{shellRouteMetaPathsJson}},
687
+ 'packages/shared-contracts/package.json',
600
688
  'packages/shared-contracts/src/index.ts',
689
+ 'packages/shared-contracts/tsconfig.json',
690
+ 'packages/shared-design-tokens/package.json',
601
691
  'packages/shared-design-tokens/src/index.ts',
602
692
  'packages/shared-design-tokens/src/tokens.css',
693
+ 'packages/shared-design-tokens/tsconfig.json',
603
694
  ];
604
695
 
605
696
  for (const vertical of fullStackVerticals) {
@@ -660,7 +751,12 @@ assert(generatedContract.node?.engineRange === '>=26', 'Generated contract must
660
751
  assert(readText('.mise.toml').includes(`node = "${expectedNodeVersion}"`), 'mise must pin the generated Node version');
661
752
  assert(readText('.mise.toml').includes(`pnpm = "${packageManagerPnpmVersion}"`), 'mise must pin the generated pnpm version');
662
753
  const workflowText = readText('.github/workflows/ultramodern-workspace-gates.yml');
663
- assert(workflowText.includes(`node-version: "${expectedNodeVersion}"`), 'CI workflow must pin the generated Node version');
754
+ const workflowNodeVersions = extractWorkflowNodeVersions(workflowText);
755
+ assert(workflowNodeVersions.length > 0, 'CI workflow must configure setup-node node-version');
756
+ assert(
757
+ workflowNodeVersions.every(nodeVersion => nodeVersion === expectedNodeVersion),
758
+ `CI workflow must pin the generated Node version ${expectedNodeVersion}; found ${workflowNodeVersions.join(', ')}`,
759
+ );
664
760
  assert(!workflowText.includes('FORCE_JAVASCRIPT_ACTIONS_TO_NODE24'), 'CI workflow must not carry the legacy Node 24 override');
665
761
  assert(rootPackage.modernjs?.preset === 'presetUltramodern', 'Root must declare presetUltramodern');
666
762
  assert(rootPackage.modernjs?.packageSource?.config === './.modernjs/ultramodern-package-source.json', 'Root must point at package source metadata');
@@ -711,6 +807,7 @@ assert(
711
807
  );
712
808
  assert(rootPackage.scripts?.['cloudflare:build'] === expectedCloudflareBuildScript, 'Root cloudflare:build script is incorrect');
713
809
  assert(!('ultramodern:check' in (rootPackage.scripts ?? {})), 'Root must not expose ultramodern:check');
810
+ assert(rootPackage.scripts?.typecheck === 'node ./scripts/ultramodern-typecheck.mjs --build tsconfig.json', 'Root typecheck must run TS-Go build mode across project references');
714
811
  assert(rootPackage.scripts?.['contract:check'] === 'node ./scripts/validate-ultramodern-workspace.mjs', 'Root must expose contract:check');
715
812
  assert(rootPackage.scripts?.['i18n:boundaries'] === 'node ./scripts/check-ultramodern-i18n-boundaries.mjs', 'Root must expose i18n:boundaries');
716
813
  assert(rootPackage.scripts?.['performance:readiness'] === 'node ./scripts/ultramodern-performance-readiness.mjs', 'Root must expose default-on performance readiness diagnostics');
@@ -723,6 +820,9 @@ assert(performanceReadinessConfig.includes("failOn: 'framework-invariant'"), 'Pe
723
820
  assert(performanceReadinessScript.includes("ULTRAMODERN_PERFORMANCE_READINESS_DIAGNOSTICS"), 'Performance readiness script must support env opt-out');
724
821
  assert(performanceReadinessScript.includes('ultramodern-performance-readiness-diagnostics-v1'), 'Performance readiness script must emit the deterministic report profile');
725
822
  const i18nBoundaryScript = readText('scripts/check-ultramodern-i18n-boundaries.mjs');
823
+ const ultramodernTypecheckScript = readText('scripts/ultramodern-typecheck.mjs');
824
+ assert(ultramodernTypecheckScript.includes("'--checkers'") && ultramodernTypecheckScript.includes("'--builders'"), 'Root typecheck script must drive TS-Go checker and builder parallelism');
825
+ assert(ultramodernTypecheckScript.includes('effect-tsgo') && ultramodernTypecheckScript.includes('get-exe-path'), 'Root typecheck script must resolve the @effect/tsgo native-preview binary');
726
826
  assert(
727
827
  i18nBoundaryScript.includes("from '@modern-js/code-tools'") &&
728
828
  i18nBoundaryScript.includes('runWorkspaceSourceCheck'),
@@ -819,6 +919,7 @@ assert(shellModernConfig.includes('assetPrefix,'), 'Shell modern.config.ts must
819
919
  assert(shellContract?.config?.dev?.assetPrefix === '/', 'Shell dev asset prefix must stay origin-relative');
820
920
  assert(shellContract?.config?.output?.assetPrefix?.default === '/', 'Shell asset prefix must default to origin-relative paths');
821
921
  assert(JSON.stringify(shellContract?.config?.output?.assetPrefix?.envFallbackOrder) === JSON.stringify(['MODERN_ASSET_PREFIX', 'ULTRAMODERN_ASSET_PREFIX']), 'Shell asset prefix env fallback order is incorrect');
922
+ assert(shellContract?.config?.output?.disableTsChecker === false, 'Shell must keep the framework TypeScript checker enabled');
822
923
  assert(shellContract?.config?.performance?.readinessDiagnostics?.default === 'enabled', 'Shell performance readiness diagnostics must be default-on');
823
924
  assert(shellContract?.config?.performance?.readinessDiagnostics?.failOn === 'framework-invariant', 'Shell performance readiness diagnostics must only fail framework invariants by default');
824
925
  assert(shellContract?.config?.performance?.readinessDiagnostics?.optOut?.env === 'ULTRAMODERN_PERFORMANCE_READINESS_DIAGNOSTICS=false', 'Shell performance readiness env opt-out is incorrect');
@@ -913,6 +1014,7 @@ for (const vertical of fullStackVerticals) {
913
1014
  assert(contractEntry?.config?.dev?.assetPrefix === '/', `${vertical.id} dev asset prefix must stay origin-relative`);
914
1015
  assert(contractEntry?.config?.output?.assetPrefix?.default === '/', `${vertical.id} asset prefix must default to origin-relative paths`);
915
1016
  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`);
1017
+ assert(contractEntry?.config?.output?.disableTsChecker === false, `${vertical.id} must keep the framework TypeScript checker enabled`);
916
1018
  assert(contractEntry?.config?.performance?.readinessDiagnostics?.default === 'enabled', `${vertical.id} performance readiness diagnostics must be default-on`);
917
1019
  assert(contractEntry?.config?.performance?.readinessDiagnostics?.failOn === 'framework-invariant', `${vertical.id} performance readiness diagnostics must only fail framework invariants by default`);
918
1020
  assert(contractEntry?.config?.performance?.readinessDiagnostics?.optOut?.config === 'scripts/ultramodern-performance-readiness.config.mjs', `${vertical.id} performance readiness opt-out config is incorrect`);