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

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 (128) hide show
  1. package/README.md +13 -11
  2. package/dist/cjs/index.cjs +37 -4
  3. package/dist/cjs/locale/en.cjs +12 -2
  4. package/dist/cjs/locale/zh.cjs +12 -2
  5. package/dist/cjs/ultramodern-tooling/commands.cjs +185 -0
  6. package/dist/cjs/ultramodern-tooling/config.cjs +172 -0
  7. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +79 -61
  8. package/dist/cjs/ultramodern-workspace/bridge-config.cjs +327 -0
  9. package/dist/cjs/ultramodern-workspace/codesmith.cjs +1 -0
  10. package/dist/cjs/ultramodern-workspace/contracts.cjs +121 -648
  11. package/dist/cjs/ultramodern-workspace/demo-components.cjs +26 -33
  12. package/dist/cjs/ultramodern-workspace/descriptors.cjs +4 -4
  13. package/dist/cjs/ultramodern-workspace/effect-api.cjs +230 -100
  14. package/dist/cjs/ultramodern-workspace/fs-io.cjs +117 -0
  15. package/dist/cjs/ultramodern-workspace/generation-result.cjs +1 -1
  16. package/dist/cjs/ultramodern-workspace/index.cjs +4 -0
  17. package/dist/cjs/ultramodern-workspace/mf-validation.cjs +571 -0
  18. package/dist/cjs/ultramodern-workspace/module-federation.cjs +51 -20
  19. package/dist/cjs/ultramodern-workspace/package-json.cjs +62 -19
  20. package/dist/cjs/ultramodern-workspace/policy.cjs +2 -2
  21. package/dist/cjs/ultramodern-workspace/public-api.cjs +4 -0
  22. package/dist/cjs/ultramodern-workspace/versions.cjs +5 -0
  23. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +80 -43
  24. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +63 -20
  25. package/dist/esm/index.js +37 -4
  26. package/dist/esm/locale/en.js +12 -2
  27. package/dist/esm/locale/zh.js +12 -2
  28. package/dist/esm/ultramodern-tooling/commands.js +132 -0
  29. package/dist/esm/ultramodern-tooling/config.js +120 -0
  30. package/dist/esm/ultramodern-workspace/add-vertical.js +81 -66
  31. package/dist/esm/ultramodern-workspace/bridge-config.js +270 -0
  32. package/dist/esm/ultramodern-workspace/codesmith.js +1 -0
  33. package/dist/esm/ultramodern-workspace/contracts.js +124 -603
  34. package/dist/esm/ultramodern-workspace/demo-components.js +26 -33
  35. package/dist/esm/ultramodern-workspace/descriptors.js +2 -2
  36. package/dist/esm/ultramodern-workspace/effect-api.js +230 -100
  37. package/dist/esm/ultramodern-workspace/fs-io.js +116 -2
  38. package/dist/esm/ultramodern-workspace/generation-result.js +2 -2
  39. package/dist/esm/ultramodern-workspace/index.js +1 -0
  40. package/dist/esm/ultramodern-workspace/mf-validation.js +516 -0
  41. package/dist/esm/ultramodern-workspace/module-federation.js +49 -21
  42. package/dist/esm/ultramodern-workspace/package-json.js +60 -20
  43. package/dist/esm/ultramodern-workspace/policy.js +2 -2
  44. package/dist/esm/ultramodern-workspace/public-api.js +1 -0
  45. package/dist/esm/ultramodern-workspace/versions.js +3 -1
  46. package/dist/esm/ultramodern-workspace/workspace-scripts.js +78 -26
  47. package/dist/esm/ultramodern-workspace/write-workspace.js +67 -27
  48. package/dist/esm-node/index.js +37 -4
  49. package/dist/esm-node/locale/en.js +12 -2
  50. package/dist/esm-node/locale/zh.js +12 -2
  51. package/dist/esm-node/ultramodern-tooling/commands.js +133 -0
  52. package/dist/esm-node/ultramodern-tooling/config.js +121 -0
  53. package/dist/esm-node/ultramodern-workspace/add-vertical.js +81 -66
  54. package/dist/esm-node/ultramodern-workspace/bridge-config.js +271 -0
  55. package/dist/esm-node/ultramodern-workspace/codesmith.js +1 -0
  56. package/dist/esm-node/ultramodern-workspace/contracts.js +124 -603
  57. package/dist/esm-node/ultramodern-workspace/demo-components.js +26 -33
  58. package/dist/esm-node/ultramodern-workspace/descriptors.js +2 -2
  59. package/dist/esm-node/ultramodern-workspace/effect-api.js +230 -100
  60. package/dist/esm-node/ultramodern-workspace/fs-io.js +116 -2
  61. package/dist/esm-node/ultramodern-workspace/generation-result.js +2 -2
  62. package/dist/esm-node/ultramodern-workspace/index.js +1 -0
  63. package/dist/esm-node/ultramodern-workspace/mf-validation.js +517 -0
  64. package/dist/esm-node/ultramodern-workspace/module-federation.js +49 -21
  65. package/dist/esm-node/ultramodern-workspace/package-json.js +60 -20
  66. package/dist/esm-node/ultramodern-workspace/policy.js +2 -2
  67. package/dist/esm-node/ultramodern-workspace/public-api.js +1 -0
  68. package/dist/esm-node/ultramodern-workspace/versions.js +3 -1
  69. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +78 -26
  70. package/dist/esm-node/ultramodern-workspace/write-workspace.js +67 -27
  71. package/dist/types/locale/en.d.ts +10 -0
  72. package/dist/types/locale/index.d.ts +20 -0
  73. package/dist/types/locale/zh.d.ts +10 -0
  74. package/dist/types/ultramodern-tooling/commands.d.ts +1 -0
  75. package/dist/types/ultramodern-tooling/config.d.ts +43 -0
  76. package/dist/types/ultramodern-workspace/add-vertical.d.ts +5 -2
  77. package/dist/types/ultramodern-workspace/bridge-config.d.ts +64 -0
  78. package/dist/types/ultramodern-workspace/codesmith.d.ts +1 -0
  79. package/dist/types/ultramodern-workspace/contracts.d.ts +2 -17
  80. package/dist/types/ultramodern-workspace/descriptors.d.ts +1 -1
  81. package/dist/types/ultramodern-workspace/fs-io.d.ts +1 -0
  82. package/dist/types/ultramodern-workspace/index.d.ts +2 -0
  83. package/dist/types/ultramodern-workspace/mf-validation.d.ts +27 -0
  84. package/dist/types/ultramodern-workspace/module-federation.d.ts +1 -0
  85. package/dist/types/ultramodern-workspace/package-json.d.ts +5 -3
  86. package/dist/types/ultramodern-workspace/public-api.d.ts +2 -0
  87. package/dist/types/ultramodern-workspace/types.d.ts +2 -0
  88. package/dist/types/ultramodern-workspace/versions.d.ts +2 -0
  89. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +2 -7
  90. package/dist/types/ultramodern-workspace/write-workspace.d.ts +4 -2
  91. package/package.json +5 -3
  92. package/template-workspace/{.agents → .codex}/skills-lock.json +12 -12
  93. package/template-workspace/.gitignore.handlebars +3 -0
  94. package/template-workspace/AGENTS.md.handlebars +11 -5
  95. package/template-workspace/README.md.handlebars +15 -12
  96. package/template-workspace/oxfmt.config.ts +1 -0
  97. package/template-workspace/oxlint.config.ts +1 -0
  98. package/template-workspace/patches/@tanstack__router-core@1.171.13.patch +51 -0
  99. package/template-workspace/pnpm-workspace.yaml.handlebars +12 -0
  100. package/template-workspace/scripts/bootstrap-agent-skills.mjs +96 -69
  101. package/templates/app/shell-frame.tsx +1 -2
  102. package/templates/app/ultramodern-route-head.tsx.handlebars +5 -6
  103. package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +59 -7
  104. package/templates/workspace-scripts/check-ultramodern-api-boundaries.mjs +339 -0
  105. package/templates/workspace-scripts/generate-public-surface-assets.mjs +245 -8
  106. package/templates/workspace-scripts/proof-cloudflare-version.mjs +276 -8
  107. package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +35 -10
  108. package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +164 -1
  109. package/templates/workspace-scripts/ultramodern-typecheck.mjs +4 -2
  110. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +949 -63
  111. /package/template-workspace/{.agents → .codex}/rstackjs-agent-skills-LICENSE +0 -0
  112. /package/template-workspace/{.agents → .codex}/skills/rsbuild-best-practices/SKILL.md +0 -0
  113. /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/SKILL.md +0 -0
  114. /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/command-map.md +0 -0
  115. /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/common-analysis-patterns.md +0 -0
  116. /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor-common.md +0 -0
  117. /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor-rspack.md +0 -0
  118. /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor-webpack.md +0 -0
  119. /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/install-rsdoctor.md +0 -0
  120. /package/template-workspace/{.agents → .codex}/skills/rsdoctor-analysis/references/rsdoctor-data-types.md +0 -0
  121. /package/template-workspace/{.agents → .codex}/skills/rslib-best-practices/SKILL.md +0 -0
  122. /package/template-workspace/{.agents → .codex}/skills/rslib-modern-package/SKILL.md +0 -0
  123. /package/template-workspace/{.agents → .codex}/skills/rspack-best-practices/SKILL.md +0 -0
  124. /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/SKILL.md +0 -0
  125. /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/references/bottlenecks.md +0 -0
  126. /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/references/tracing-guide.md +0 -0
  127. /package/template-workspace/{.agents → .codex}/skills/rspack-tracing/scripts/analyze_trace.js +0 -0
  128. /package/template-workspace/{.agents → .codex}/skills/rstest-best-practices/SKILL.md +0 -0
@@ -2,15 +2,58 @@ import fs from 'node:fs';
2
2
  import path from 'node:path';
3
3
 
4
4
  const root = process.cwd();
5
- const generatedContractPath = path.join(
6
- root,
7
- '.modernjs/ultramodern-generated-contract.json',
8
- );
9
- const generatedContract = fs.existsSync(generatedContractPath)
10
- ? JSON.parse(fs.readFileSync(generatedContractPath, 'utf-8'))
11
- : undefined;
5
+ const compactConfigPath = path.join(root, '.modernjs/ultramodern.json');
12
6
  const defaultAppDirs = {{defaultAppDirsJson}};
13
7
 
8
+ function readJson(filePath) {
9
+ return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
10
+ }
11
+
12
+ function normalizeRelativePath(value) {
13
+ return String(value ?? '').replace(/\\/gu, '/').replace(/^\.\/+/u, '');
14
+ }
15
+
16
+ function readGeneratedContractView() {
17
+ if (fs.existsSync(compactConfigPath)) {
18
+ const compactConfig = readJson(compactConfigPath);
19
+ return {
20
+ sourcePath: compactConfigPath,
21
+ apps: Array.isArray(compactConfig.topology?.apps)
22
+ ? compactConfig.topology.apps.map(app => {
23
+ const moduleFederation =
24
+ app.moduleFederation && typeof app.moduleFederation === 'object'
25
+ ? app.moduleFederation
26
+ : {};
27
+ return {
28
+ id: String(app.id),
29
+ path:
30
+ typeof app.path === 'string'
31
+ ? normalizeRelativePath(app.path)
32
+ : '.',
33
+ moduleFederation: {
34
+ exposes: Array.isArray(moduleFederation.exposes)
35
+ ? moduleFederation.exposes.filter(
36
+ expose => typeof expose === 'string',
37
+ )
38
+ : [],
39
+ dts: {
40
+ compilerInstance:
41
+ moduleFederation.dts?.compilerInstance ?? 'tsgo',
42
+ tsConfigPath:
43
+ moduleFederation.dts?.tsConfigPath ??
44
+ './tsconfig.mf-types.json',
45
+ },
46
+ },
47
+ };
48
+ })
49
+ : [],
50
+ };
51
+ }
52
+ throw new Error('Missing UltraModern config. Expected .modernjs/ultramodern.json.');
53
+ }
54
+
55
+ const generatedContract = readGeneratedContractView();
56
+
14
57
  const args = process.argv.slice(2);
15
58
  if (args.includes('--help') || args.includes('-h')) {
16
59
  process.stdout.write(`Usage:
@@ -48,6 +91,15 @@ for (const appDir of appDirs) {
48
91
  `Module Federation DTS must use the workspace TypeScript compiler: ${appDir}`,
49
92
  );
50
93
  }
94
+ if (
95
+ contractEntry &&
96
+ contractEntry.moduleFederation?.dts?.tsConfigPath !==
97
+ './tsconfig.mf-types.json'
98
+ ) {
99
+ throw new Error(
100
+ `Module Federation DTS must use the dedicated public-surface tsconfig: ${appDir}`,
101
+ );
102
+ }
51
103
 
52
104
  if (contractEntry && contractEntry.moduleFederation?.exposes?.length === 0) {
53
105
  continue;
@@ -0,0 +1,339 @@
1
+ #!/usr/bin/env node
2
+ import fs from 'node:fs';
3
+ import path from 'node:path';
4
+
5
+ const workspaceRoot = process.env.ULTRAMODERN_WORKSPACE_ROOT ?? process.cwd();
6
+ const failures = [];
7
+
8
+ const ignoredDirectories = new Set([
9
+ '.git',
10
+ '.modern',
11
+ '.output',
12
+ 'coverage',
13
+ 'dist',
14
+ 'node_modules',
15
+ 'repos',
16
+ ]);
17
+
18
+ function normalize(filePath) {
19
+ return filePath.split(path.sep).join('/');
20
+ }
21
+
22
+ function relative(filePath) {
23
+ return normalize(path.relative(workspaceRoot, filePath));
24
+ }
25
+
26
+ function exists(relativePath) {
27
+ return fs.existsSync(path.join(workspaceRoot, relativePath));
28
+ }
29
+
30
+ function readText(relativePath) {
31
+ return fs.readFileSync(path.join(workspaceRoot, relativePath), 'utf8');
32
+ }
33
+
34
+ function fail(message) {
35
+ failures.push(message);
36
+ }
37
+
38
+ function assert(condition, message) {
39
+ if (!condition) {
40
+ fail(message);
41
+ }
42
+ }
43
+
44
+ function listFiles(startDirectory) {
45
+ const absoluteStart = path.join(workspaceRoot, startDirectory);
46
+ if (!fs.existsSync(absoluteStart)) {
47
+ return [];
48
+ }
49
+
50
+ const files = [];
51
+ const visit = directory => {
52
+ for (const entry of fs.readdirSync(directory, { withFileTypes: true })) {
53
+ if (ignoredDirectories.has(entry.name)) {
54
+ continue;
55
+ }
56
+
57
+ const absoluteEntry = path.join(directory, entry.name);
58
+ if (entry.isDirectory()) {
59
+ visit(absoluteEntry);
60
+ continue;
61
+ }
62
+
63
+ if (entry.isFile()) {
64
+ files.push(relative(absoluteEntry));
65
+ }
66
+ }
67
+ };
68
+
69
+ visit(absoluteStart);
70
+ return files;
71
+ }
72
+
73
+ function listDirectories(startDirectory) {
74
+ const absoluteStart = path.join(workspaceRoot, startDirectory);
75
+ if (!fs.existsSync(absoluteStart)) {
76
+ return [];
77
+ }
78
+
79
+ return fs
80
+ .readdirSync(absoluteStart, { withFileTypes: true })
81
+ .filter(entry => entry.isDirectory() && !ignoredDirectories.has(entry.name))
82
+ .map(entry => path.posix.join(startDirectory, entry.name));
83
+ }
84
+
85
+ function assertNoPath(relativePath, message) {
86
+ if (exists(relativePath)) {
87
+ fail(message);
88
+ }
89
+ }
90
+
91
+ function assertContains(relativePath, content, pattern, message) {
92
+ assert(pattern.test(content), `${relativePath}: ${message}`);
93
+ }
94
+
95
+ function assertNotContains(relativePath, content, pattern, message) {
96
+ assert(!pattern.test(content), `${relativePath}: ${message}`);
97
+ }
98
+
99
+ const generatedFiles = [
100
+ ...listFiles('apps'),
101
+ ...listFiles('verticals'),
102
+ ...listFiles('packages'),
103
+ ];
104
+
105
+ for (const forbiddenPath of [
106
+ 'apps/shell-super-app/src/effect',
107
+ ...listDirectories('verticals').flatMap(verticalPath => [
108
+ `${verticalPath}/api/effect`,
109
+ `${verticalPath}/api/lambda`,
110
+ `${verticalPath}/shared/effect`,
111
+ `${verticalPath}/src/effect`,
112
+ ]),
113
+ ]) {
114
+ assertNoPath(
115
+ forbiddenPath,
116
+ `${forbiddenPath} is forbidden in UltraModern strictEffectApproach workspaces; use api/index.ts, shared/api.ts and src/api/* instead.`,
117
+ );
118
+ }
119
+
120
+ const textFiles = generatedFiles.filter(file =>
121
+ /\.(?:[cm]?[jt]sx?|json|md|mjs|mts|cts)$/u.test(file),
122
+ );
123
+
124
+ for (const file of textFiles) {
125
+ const content = readText(file);
126
+
127
+ if (/\/api\//u.test(file)) {
128
+ assertNotContains(
129
+ file,
130
+ content,
131
+ /\bnew\s+Response\s*\(|\bResponse\.json\s*\(/u,
132
+ 'API modules must not hand-build Response objects; model the endpoint through Effect HttpApi and schemas.',
133
+ );
134
+ assertNotContains(
135
+ file,
136
+ content,
137
+ /\brequest\.(?:json|text|formData|arrayBuffer)\s*\(/u,
138
+ 'API modules must not manually parse request bodies; use HttpApiEndpoint payload/query/params schemas.',
139
+ );
140
+ assertNotContains(
141
+ file,
142
+ content,
143
+ /\bexport\s+const\s+handler\b|\bexport\s+default\s+async\b/u,
144
+ 'API modules must not export raw request handlers; export defineEffectBff(...) from api/index.ts.',
145
+ );
146
+ assertNotContains(
147
+ file,
148
+ content,
149
+ /\bcreateHandler\s*[:=]\s*(?!defineEffectBff\b)/u,
150
+ 'API modules must not define unbranded handler factories; use defineEffectBff(...).',
151
+ );
152
+ }
153
+
154
+ assertNotContains(
155
+ file,
156
+ content,
157
+ /@modern-js\/plugin-bff\/hono-server/u,
158
+ 'Hono server helpers are not part of generated UltraModern API workspaces.',
159
+ );
160
+ assertNotContains(
161
+ file,
162
+ content,
163
+ /runtimeFramework:\s*['"]hono['"]/u,
164
+ 'Generated UltraModern API apps must use the Effect runtime.',
165
+ );
166
+ }
167
+
168
+ const shellClient = 'apps/shell-super-app/src/api/vertical-clients.ts';
169
+ if (exists('apps/shell-super-app')) {
170
+ assert(exists(shellClient), `${shellClient} must aggregate vertical API clients.`);
171
+ }
172
+
173
+ for (const verticalPath of listDirectories('verticals')) {
174
+ const apiEntry = `${verticalPath}/api/index.ts`;
175
+ const sharedApi = `${verticalPath}/shared/api.ts`;
176
+ const srcApiDirectory = `${verticalPath}/src/api`;
177
+ const modernConfig = `${verticalPath}/modern.config.ts`;
178
+ const packageJsonPath = `${verticalPath}/package.json`;
179
+
180
+ assert(exists(apiEntry), `${apiEntry} is required.`);
181
+ assert(exists(sharedApi), `${sharedApi} is required.`);
182
+ assert(exists(srcApiDirectory), `${srcApiDirectory} is required.`);
183
+
184
+ if (exists(srcApiDirectory)) {
185
+ const clientFiles = listFiles(srcApiDirectory).filter(file =>
186
+ /-client\.ts$/u.test(file),
187
+ );
188
+ assert(
189
+ clientFiles.length > 0,
190
+ `${srcApiDirectory} must contain a generated API client.`,
191
+ );
192
+ }
193
+
194
+ if (exists(apiEntry)) {
195
+ const entry = readText(apiEntry);
196
+ assertContains(
197
+ apiEntry,
198
+ entry,
199
+ /\bdefineEffectBff\b/u,
200
+ 'must export a defineEffectBff(...) runtime definition.',
201
+ );
202
+ assertContains(
203
+ apiEntry,
204
+ entry,
205
+ /\bHttpApiBuilder\b/u,
206
+ 'must implement handlers through HttpApiBuilder.',
207
+ );
208
+ assertContains(
209
+ apiEntry,
210
+ entry,
211
+ /\bLayer\b/u,
212
+ 'must compose dependencies with Effect Layer.',
213
+ );
214
+ assertContains(
215
+ apiEntry,
216
+ entry,
217
+ /from ['"]\.\.\/shared\/api\.ts['"]/u,
218
+ 'must import the contract from ../shared/api.ts.',
219
+ );
220
+ }
221
+
222
+ if (exists(sharedApi)) {
223
+ const contract = readText(sharedApi);
224
+ assertContains(
225
+ sharedApi,
226
+ contract,
227
+ /\bHttpApi\.make\b/u,
228
+ 'must declare the HttpApi contract.',
229
+ );
230
+ assertContains(
231
+ sharedApi,
232
+ contract,
233
+ /\bHttpApiGroup\.make\b/u,
234
+ 'must declare HttpApi groups.',
235
+ );
236
+ assertContains(
237
+ sharedApi,
238
+ contract,
239
+ /\bHttpApiEndpoint\./u,
240
+ 'must declare endpoints through HttpApiEndpoint.',
241
+ );
242
+ assertContains(
243
+ sharedApi,
244
+ contract,
245
+ /\bSchema\./u,
246
+ 'must use Schema for request, response and error shapes.',
247
+ );
248
+ }
249
+
250
+ if (exists(modernConfig)) {
251
+ const config = readText(modernConfig);
252
+ assertContains(
253
+ modernConfig,
254
+ config,
255
+ /runtimeFramework:\s*['"]effect['"]/u,
256
+ 'must use bff.runtimeFramework: effect.',
257
+ );
258
+ assertContains(
259
+ modernConfig,
260
+ config,
261
+ /entry:\s*['"]\.\/api\/index['"]/u,
262
+ 'must point bff.effect.entry at ./api/index.',
263
+ );
264
+ assertContains(
265
+ modernConfig,
266
+ config,
267
+ /strictEffectApproach:\s*true/u,
268
+ 'must enable strictEffectApproach explicitly.',
269
+ );
270
+ }
271
+
272
+ if (exists(packageJsonPath)) {
273
+ const packageJson = JSON.parse(readText(packageJsonPath));
274
+ assert(
275
+ packageJson.exports?.['./api'] === './shared/api.ts',
276
+ `${packageJsonPath}: package must export ./api from shared/api.ts.`,
277
+ );
278
+ assert(
279
+ typeof packageJson.exports?.['./api/client'] === 'string' &&
280
+ packageJson.exports['./api/client'].startsWith('./src/api/'),
281
+ `${packageJsonPath}: package must export ./api/client from src/api/*.`,
282
+ );
283
+ }
284
+ }
285
+
286
+ if (exists('apps/shell-super-app/package.json')) {
287
+ const shellPackageJson = JSON.parse(
288
+ readText('apps/shell-super-app/package.json'),
289
+ );
290
+ assert(
291
+ shellPackageJson.exports?.['./api/clients'] ===
292
+ './src/api/vertical-clients.ts',
293
+ 'apps/shell-super-app/package.json must export ./api/clients.',
294
+ );
295
+ }
296
+
297
+ if (exists('package.json')) {
298
+ const rootPackageJson = JSON.parse(readText('package.json'));
299
+ assert(
300
+ rootPackageJson.scripts?.['api:check'] ===
301
+ 'node ./scripts/check-ultramodern-api-boundaries.mjs',
302
+ 'Root package.json must expose api:check.',
303
+ );
304
+ assert(
305
+ rootPackageJson.scripts?.check?.includes('pnpm api:check'),
306
+ 'Root check script must include pnpm api:check.',
307
+ );
308
+ }
309
+
310
+ if (exists('topology/reference-topology.json')) {
311
+ const topology = JSON.parse(readText('topology/reference-topology.json'));
312
+ for (const vertical of topology.verticals ?? []) {
313
+ if (vertical.api?.runtime === 'effect') {
314
+ assert(
315
+ vertical.api.bff?.strictEffectApproach === true,
316
+ `${vertical.id} topology must mark strictEffectApproach as true.`,
317
+ );
318
+ assert(
319
+ typeof vertical.api.serverEntry === 'string' &&
320
+ vertical.api.serverEntry.endsWith('/api/index.ts'),
321
+ `${vertical.id} topology must use api/index.ts as the server entry.`,
322
+ );
323
+ }
324
+ assert(
325
+ !vertical.api?.effect,
326
+ `${vertical.id} topology must describe the API directly, not under api.effect.`,
327
+ );
328
+ }
329
+ }
330
+
331
+ if (failures.length > 0) {
332
+ console.error('UltraModern API boundary check failed:');
333
+ for (const failure of failures) {
334
+ console.error(`- ${failure}`);
335
+ }
336
+ process.exit(1);
337
+ }
338
+
339
+ console.log('UltraModern API boundary check passed.');
@@ -4,18 +4,255 @@ import path from 'node:path';
4
4
  import { fileURLToPath, pathToFileURL } from 'node:url';
5
5
 
6
6
  const workspaceRoot = path.resolve(
7
- path.dirname(fileURLToPath(import.meta.url)),
8
- '..',
9
- );
10
- const contractPath = path.join(
11
- workspaceRoot,
12
- '.modernjs/ultramodern-generated-contract.json',
7
+ process.env.ULTRAMODERN_WORKSPACE_ROOT ??
8
+ path.join(path.dirname(fileURLToPath(import.meta.url)), '..'),
13
9
  );
10
+ const compactConfigPath = path.join(workspaceRoot, '.modernjs/ultramodern.json');
14
11
 
15
12
  function readJson(filePath) {
16
13
  return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
17
14
  }
18
15
 
16
+ function toKebabCase(value) {
17
+ return String(value)
18
+ .trim()
19
+ .replace(/([a-z0-9])([A-Z])/gu, '$1-$2')
20
+ .replace(/[^a-zA-Z0-9._-]+/gu, '-')
21
+ .replace(/[._]+/gu, '-')
22
+ .toLowerCase()
23
+ .replace(/-+/gu, '-')
24
+ .replace(/^-+|-+$/gu, '');
25
+ }
26
+
27
+ function toEnvSegment(value) {
28
+ return toKebabCase(value).replace(/-/gu, '_').toUpperCase();
29
+ }
30
+
31
+ function normalizeRelativePath(value) {
32
+ return String(value ?? '').replace(/\\/gu, '/').replace(/^\.\/+/u, '');
33
+ }
34
+
35
+ function appNamespace(app) {
36
+ return app.kind === 'shell' ? 'shell' : (app.domain ?? app.id);
37
+ }
38
+
39
+ function defaultPortEnv(app) {
40
+ return app.kind === 'shell'
41
+ ? 'SHELL_SUPER_APP_PORT'
42
+ : `VERTICAL_${toEnvSegment(app.domain ?? app.id)}_PORT`;
43
+ }
44
+
45
+ function defaultPort(app) {
46
+ return app.kind === 'shell' ? 3020 : 3030;
47
+ }
48
+
49
+ function normalizeCompactApp(rawApp) {
50
+ const id = String(rawApp.id);
51
+ const kind = rawApp.kind === 'vertical' ? 'vertical' : 'shell';
52
+ const appPath =
53
+ typeof rawApp.path === 'string'
54
+ ? normalizeRelativePath(rawApp.path)
55
+ : kind === 'shell'
56
+ ? 'apps/shell-super-app'
57
+ : `verticals/${toKebabCase(id)}`;
58
+ const packageSuffix =
59
+ typeof rawApp.packageSuffix === 'string'
60
+ ? rawApp.packageSuffix
61
+ : appPath.split('/').at(-1) ?? id;
62
+ const domain =
63
+ typeof rawApp.domain === 'string'
64
+ ? rawApp.domain
65
+ : kind === 'vertical'
66
+ ? packageSuffix
67
+ : undefined;
68
+ const moduleFederation =
69
+ rawApp.moduleFederation && typeof rawApp.moduleFederation === 'object'
70
+ ? rawApp.moduleFederation
71
+ : {};
72
+
73
+ return {
74
+ id,
75
+ kind,
76
+ path: appPath,
77
+ packageSuffix,
78
+ domain,
79
+ port:
80
+ typeof rawApp.port === 'number'
81
+ ? rawApp.port
82
+ : defaultPort({ id, kind, domain }),
83
+ portEnv:
84
+ typeof rawApp.portEnv === 'string'
85
+ ? rawApp.portEnv
86
+ : defaultPortEnv({ id, kind, domain }),
87
+ mfName:
88
+ typeof moduleFederation.name === 'string'
89
+ ? moduleFederation.name
90
+ : kind === 'shell'
91
+ ? 'shellSuperApp'
92
+ : `vertical${toKebabCase(domain ?? id)
93
+ .split('-')
94
+ .filter(Boolean)
95
+ .map(part => part.charAt(0).toUpperCase() + part.slice(1))
96
+ .join('')}`,
97
+ exposes: Array.isArray(moduleFederation.exposes)
98
+ ? moduleFederation.exposes.filter(expose => typeof expose === 'string')
99
+ : [],
100
+ verticalRefs: Array.isArray(moduleFederation.verticalRefs)
101
+ ? moduleFederation.verticalRefs.filter(ref => typeof ref === 'string')
102
+ : [],
103
+ marker: {
104
+ appId: id,
105
+ },
106
+ routes:
107
+ rawApp.routes && typeof rawApp.routes === 'object'
108
+ ? rawApp.routes
109
+ : undefined,
110
+ effectApi:
111
+ rawApp.effectApi && typeof rawApp.effectApi === 'object'
112
+ ? {
113
+ stem:
114
+ typeof rawApp.effectApi.stem === 'string'
115
+ ? rawApp.effectApi.stem
116
+ : domain ?? id,
117
+ prefix:
118
+ typeof rawApp.effectApi.prefix === 'string'
119
+ ? rawApp.effectApi.prefix
120
+ : `/${domain ?? id}-api`,
121
+ }
122
+ : undefined,
123
+ };
124
+ }
125
+
126
+ function createRouteOwnedEntries(app) {
127
+ const namespace = appNamespace(app);
128
+ const base = {
129
+ descriptionKey: `${namespace}.seo.description`,
130
+ mfBoundaryId: app.mfName,
131
+ namespace,
132
+ ownerAppId: app.id,
133
+ public: false,
134
+ indexable: false,
135
+ publicSurface: 'private-app-screen',
136
+ };
137
+
138
+ return [
139
+ {
140
+ ...base,
141
+ canonicalPath: '/',
142
+ id: app.kind === 'shell' ? 'shell-home' : `${app.id}-home`,
143
+ localisedPaths: {
144
+ cs: '/',
145
+ en: '/',
146
+ },
147
+ titleKey: app.kind === 'shell' ? 'shell.title' : `${namespace}.title`,
148
+ },
149
+ ];
150
+ }
151
+
152
+ function createPublicRoutes(app) {
153
+ return createRouteOwnedEntries(app)
154
+ .filter(route => route.public && route.indexable)
155
+ .map(route => ({
156
+ canonicalPath: route.canonicalPath,
157
+ id: route.id,
158
+ localisedPaths: route.localisedPaths,
159
+ namespace: route.namespace,
160
+ ownerAppId: route.ownerAppId,
161
+ descriptionKey: route.descriptionKey,
162
+ titleKey: route.titleKey,
163
+ }));
164
+ }
165
+
166
+ function createPublicSurface(app) {
167
+ const publicRoutes = createPublicRoutes(app);
168
+ const basePublicSurface = {
169
+ authoring: 'colocated-route-meta',
170
+ artifactLifecycle: 'build-and-deploy-output',
171
+ generatedManifest: './src/routes/ultramodern-route-metadata',
172
+ source: 'route-owned-public-routes',
173
+ metadataExport: './src/routes/ultramodern-route-metadata',
174
+ generator: 'scripts/generate-public-surface-assets.mjs',
175
+ outputRoot: 'dist/public',
176
+ cloudflareOutputRoot: '.output/public',
177
+ privateRoutePolicy: 'omit-from-generated-public-surface',
178
+ files:
179
+ publicRoutes.length > 0
180
+ ? ['robots.txt', 'sitemap.xml', 'site.webmanifest']
181
+ : ['robots.txt'],
182
+ omittedByDefault: ['api-catalog.json', 'llms.txt', 'security.txt'],
183
+ languages: ['en', 'cs'],
184
+ contentExpansion: {
185
+ authoring: 'route-owned-esm-provider',
186
+ defaultProviderFile: 'route.sitemap.mjs',
187
+ entryExport: 'default-or-entries',
188
+ paramsSource: 'params-or-localeParams',
189
+ draftPolicy: 'omit-draft-by-default',
190
+ indexablePolicy: 'omit-indexable-false',
191
+ lifecycle: 'executed-during-public-surface-generation',
192
+ },
193
+ contentSources: [],
194
+ publicRoutes,
195
+ routeEntries: [],
196
+ concreteUrlPaths: [],
197
+ };
198
+
199
+ return app.routes?.publicSurface &&
200
+ typeof app.routes.publicSurface === 'object'
201
+ ? {
202
+ ...basePublicSurface,
203
+ ...app.routes.publicSurface,
204
+ }
205
+ : basePublicSurface;
206
+ }
207
+
208
+ function createCloudflareDeploy(config, app) {
209
+ const packageScope =
210
+ typeof config.workspace?.packageScope === 'string'
211
+ ? config.workspace.packageScope
212
+ : path.basename(workspaceRoot);
213
+ const compatibilityDate =
214
+ typeof config.deploy?.worker?.compatibilityDate === 'string'
215
+ ? config.deploy.worker.compatibilityDate
216
+ : '2026-06-02';
217
+ return {
218
+ workerName: `${toKebabCase(packageScope)}-${app.packageSuffix}`.slice(0, 63),
219
+ publicUrlEnv: `ULTRAMODERN_PUBLIC_URL_${toEnvSegment(app.id)}`,
220
+ compatibilityDate,
221
+ compatibilityFlags: ['nodejs_compat', 'global_fetch_strictly_public'],
222
+ };
223
+ }
224
+
225
+ function synthesizeContractFromCompactConfig(config) {
226
+ const apps = Array.isArray(config.topology?.apps)
227
+ ? config.topology.apps.map(normalizeCompactApp)
228
+ : [];
229
+
230
+ return {
231
+ sourcePath: compactConfigPath,
232
+ apps: apps.map(app => ({
233
+ ...app,
234
+ deploy: {
235
+ cloudflare: createCloudflareDeploy(config, app),
236
+ },
237
+ routes: {
238
+ publicSurface: createPublicSurface(app),
239
+ },
240
+ })),
241
+ };
242
+ }
243
+
244
+ function readGeneratedContractView() {
245
+ if (fs.existsSync(compactConfigPath)) {
246
+ return synthesizeContractFromCompactConfig(readJson(compactConfigPath));
247
+ }
248
+ throw new Error(
249
+ `Missing UltraModern config. Expected ${path.relative(
250
+ workspaceRoot,
251
+ compactConfigPath,
252
+ )}.`,
253
+ );
254
+ }
255
+
19
256
  function parseArgs(argv) {
20
257
  const parsed = {
21
258
  appId: undefined,
@@ -517,10 +754,10 @@ try {
517
754
  printHelp();
518
755
  process.exit(0);
519
756
  }
520
- const contract = readJson(contractPath);
757
+ const contract = readGeneratedContractView();
521
758
  const app = contract.apps?.find(candidate => candidate.id === args.appId);
522
759
  if (!app) {
523
- throw new Error(`Unknown app in generated contract: ${args.appId}`);
760
+ throw new Error(`Unknown app in UltraModern config: ${args.appId}`);
524
761
  }
525
762
  await generatePublicSurfaceAssets(app, args.target, args.requirePublicOrigin);
526
763
  } catch (error) {