@bleedingdev/modern-js-create 3.4.0-ultramodern.19 → 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 (56) hide show
  1. package/dist/cjs/index.cjs +1 -1
  2. package/dist/cjs/locale/en.cjs +2 -2
  3. package/dist/cjs/locale/zh.cjs +2 -2
  4. package/dist/cjs/ultramodern-tooling/config.cjs +9 -91
  5. package/dist/cjs/ultramodern-workspace/add-vertical.cjs +13 -40
  6. package/dist/cjs/ultramodern-workspace/contracts.cjs +2 -0
  7. package/dist/cjs/ultramodern-workspace/demo-components.cjs +12 -12
  8. package/dist/cjs/ultramodern-workspace/descriptors.cjs +0 -4
  9. package/dist/cjs/ultramodern-workspace/effect-api.cjs +74 -75
  10. package/dist/cjs/ultramodern-workspace/mf-validation.cjs +1 -2
  11. package/dist/cjs/ultramodern-workspace/module-federation.cjs +3 -2
  12. package/dist/cjs/ultramodern-workspace/package-json.cjs +6 -5
  13. package/dist/cjs/ultramodern-workspace/policy.cjs +1 -1
  14. package/dist/cjs/ultramodern-workspace/workspace-scripts.cjs +7 -0
  15. package/dist/cjs/ultramodern-workspace/write-workspace.cjs +4 -4
  16. package/dist/esm/index.js +1 -1
  17. package/dist/esm/locale/en.js +2 -2
  18. package/dist/esm/locale/zh.js +2 -2
  19. package/dist/esm/ultramodern-tooling/config.js +10 -92
  20. package/dist/esm/ultramodern-workspace/add-vertical.js +14 -41
  21. package/dist/esm/ultramodern-workspace/contracts.js +2 -0
  22. package/dist/esm/ultramodern-workspace/demo-components.js +12 -12
  23. package/dist/esm/ultramodern-workspace/descriptors.js +1 -2
  24. package/dist/esm/ultramodern-workspace/effect-api.js +74 -75
  25. package/dist/esm/ultramodern-workspace/mf-validation.js +1 -2
  26. package/dist/esm/ultramodern-workspace/module-federation.js +3 -2
  27. package/dist/esm/ultramodern-workspace/package-json.js +6 -5
  28. package/dist/esm/ultramodern-workspace/policy.js +1 -1
  29. package/dist/esm/ultramodern-workspace/workspace-scripts.js +5 -1
  30. package/dist/esm/ultramodern-workspace/write-workspace.js +4 -4
  31. package/dist/esm-node/index.js +1 -1
  32. package/dist/esm-node/locale/en.js +2 -2
  33. package/dist/esm-node/locale/zh.js +2 -2
  34. package/dist/esm-node/ultramodern-tooling/config.js +10 -92
  35. package/dist/esm-node/ultramodern-workspace/add-vertical.js +14 -41
  36. package/dist/esm-node/ultramodern-workspace/contracts.js +2 -0
  37. package/dist/esm-node/ultramodern-workspace/demo-components.js +12 -12
  38. package/dist/esm-node/ultramodern-workspace/descriptors.js +1 -2
  39. package/dist/esm-node/ultramodern-workspace/effect-api.js +74 -75
  40. package/dist/esm-node/ultramodern-workspace/mf-validation.js +1 -2
  41. package/dist/esm-node/ultramodern-workspace/module-federation.js +3 -2
  42. package/dist/esm-node/ultramodern-workspace/package-json.js +6 -5
  43. package/dist/esm-node/ultramodern-workspace/policy.js +1 -1
  44. package/dist/esm-node/ultramodern-workspace/workspace-scripts.js +5 -1
  45. package/dist/esm-node/ultramodern-workspace/write-workspace.js +4 -4
  46. package/dist/types/ultramodern-tooling/config.d.ts +1 -2
  47. package/dist/types/ultramodern-workspace/descriptors.d.ts +0 -1
  48. package/dist/types/ultramodern-workspace/workspace-scripts.d.ts +1 -0
  49. package/package.json +3 -3
  50. package/templates/workspace-scripts/assert-mf-types.mjs.handlebars +1 -11
  51. package/templates/workspace-scripts/check-ultramodern-api-boundaries.mjs +339 -0
  52. package/templates/workspace-scripts/generate-public-surface-assets.mjs +14 -12
  53. package/templates/workspace-scripts/proof-cloudflare-version.mjs +3 -13
  54. package/templates/workspace-scripts/ultramodern-cloudflare-proof.mjs +3 -3
  55. package/templates/workspace-scripts/ultramodern-performance-readiness.mjs +1 -9
  56. package/templates/workspace-scripts/validate-ultramodern-workspace.mjs.handlebars +82 -86
@@ -2,10 +2,6 @@ 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
5
  const compactConfigPath = path.join(root, '.modernjs/ultramodern.json');
10
6
  const defaultAppDirs = {{defaultAppDirsJson}};
11
7
 
@@ -53,13 +49,7 @@ function readGeneratedContractView() {
53
49
  : [],
54
50
  };
55
51
  }
56
- if (fs.existsSync(generatedContractPath)) {
57
- return {
58
- sourcePath: generatedContractPath,
59
- ...readJson(generatedContractPath),
60
- };
61
- }
62
- return undefined;
52
+ throw new Error('Missing UltraModern config. Expected .modernjs/ultramodern.json.');
63
53
  }
64
54
 
65
55
  const generatedContract = readGeneratedContractView();
@@ -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.');
@@ -7,10 +7,6 @@ const workspaceRoot = path.resolve(
7
7
  process.env.ULTRAMODERN_WORKSPACE_ROOT ??
8
8
  path.join(path.dirname(fileURLToPath(import.meta.url)), '..'),
9
9
  );
10
- const contractPath = path.join(
11
- workspaceRoot,
12
- '.modernjs/ultramodern-generated-contract.json',
13
- );
14
10
  const compactConfigPath = path.join(workspaceRoot, '.modernjs/ultramodern.json');
15
11
 
16
12
  function readJson(filePath) {
@@ -107,6 +103,10 @@ function normalizeCompactApp(rawApp) {
107
103
  marker: {
108
104
  appId: id,
109
105
  },
106
+ routes:
107
+ rawApp.routes && typeof rawApp.routes === 'object'
108
+ ? rawApp.routes
109
+ : undefined,
110
110
  effectApi:
111
111
  rawApp.effectApi && typeof rawApp.effectApi === 'object'
112
112
  ? {
@@ -165,7 +165,7 @@ function createPublicRoutes(app) {
165
165
 
166
166
  function createPublicSurface(app) {
167
167
  const publicRoutes = createPublicRoutes(app);
168
- return {
168
+ const basePublicSurface = {
169
169
  authoring: 'colocated-route-meta',
170
170
  artifactLifecycle: 'build-and-deploy-output',
171
171
  generatedManifest: './src/routes/ultramodern-route-metadata',
@@ -195,6 +195,14 @@ function createPublicSurface(app) {
195
195
  routeEntries: [],
196
196
  concreteUrlPaths: [],
197
197
  };
198
+
199
+ return app.routes?.publicSurface &&
200
+ typeof app.routes.publicSurface === 'object'
201
+ ? {
202
+ ...basePublicSurface,
203
+ ...app.routes.publicSurface,
204
+ }
205
+ : basePublicSurface;
198
206
  }
199
207
 
200
208
  function createCloudflareDeploy(config, app) {
@@ -237,17 +245,11 @@ function readGeneratedContractView() {
237
245
  if (fs.existsSync(compactConfigPath)) {
238
246
  return synthesizeContractFromCompactConfig(readJson(compactConfigPath));
239
247
  }
240
- if (fs.existsSync(contractPath)) {
241
- return {
242
- sourcePath: contractPath,
243
- ...readJson(contractPath),
244
- };
245
- }
246
248
  throw new Error(
247
249
  `Missing UltraModern config. Expected ${path.relative(
248
250
  workspaceRoot,
249
251
  compactConfigPath,
250
- )} or ${path.relative(workspaceRoot, contractPath)}.`,
252
+ )}.`,
251
253
  );
252
254
  }
253
255
 
@@ -9,10 +9,6 @@ const workspaceRoot = path.resolve(
9
9
  process.env.ULTRAMODERN_WORKSPACE_ROOT ??
10
10
  path.join(path.dirname(fileURLToPath(import.meta.url)), '..'),
11
11
  );
12
- const contractPath = path.join(
13
- workspaceRoot,
14
- '.modernjs/ultramodern-generated-contract.json',
15
- );
16
12
  const compactConfigPath = path.join(workspaceRoot, '.modernjs/ultramodern.json');
17
13
  const defaultOut = path.join(
18
14
  workspaceRoot,
@@ -219,7 +215,7 @@ function createCloudflareRoutes(app) {
219
215
  locale: `/locales/en/${appNamespace(app)}.json`,
220
216
  ...(app.effectApi
221
217
  ? {
222
- effectReadiness: `${app.effectApi.prefix}/effect/${app.effectApi.stem}/readiness`,
218
+ apiReadiness: `${app.effectApi.prefix}/${app.effectApi.stem}/readiness`,
223
219
  }
224
220
  : {}),
225
221
  };
@@ -285,17 +281,11 @@ function readGeneratedContractView() {
285
281
  if (fs.existsSync(compactConfigPath)) {
286
282
  return synthesizeContractFromCompactConfig(readJson(compactConfigPath));
287
283
  }
288
- if (fs.existsSync(contractPath)) {
289
- return {
290
- sourcePath: contractPath,
291
- ...readJson(contractPath),
292
- };
293
- }
294
284
  throw new Error(
295
285
  `Missing UltraModern config. Expected ${path.relative(
296
286
  workspaceRoot,
297
287
  compactConfigPath,
298
- )} or ${path.relative(workspaceRoot, contractPath)}.`,
288
+ )}.`,
299
289
  );
300
290
  }
301
291
 
@@ -379,7 +369,7 @@ async function main(argv = process.argv.slice(2)) {
379
369
  schemaVersion: 1,
380
370
  generatedAt: new Date().toISOString(),
381
371
  status: results.length > 0 ? 'pass' : 'skipped',
382
- contractPath: contract.sourcePath ?? contractPath,
372
+ contractPath: contract.sourcePath ?? compactConfigPath,
383
373
  results,
384
374
  skipped,
385
375
  };
@@ -840,13 +840,13 @@ async function validateI18nEvidence(evidence, app, publicUrl, routes) {
840
840
  }
841
841
 
842
842
  async function validateReadinessEvidence(evidence, app, publicUrl, routes) {
843
- if (routes.effectReadiness) {
844
- const readiness = await fetchText(joinUrl(publicUrl, routes.effectReadiness));
843
+ if (routes.apiReadiness) {
844
+ const readiness = await fetchText(joinUrl(publicUrl, routes.apiReadiness));
845
845
  const readinessJson = parseMaybeJson(readiness.body);
846
846
  const apiMarker = markerFromJson(readinessJson);
847
847
  evidence.assertions.push({
848
848
  type: 'api-marker',
849
- route: routes.effectReadiness,
849
+ route: routes.apiReadiness,
850
850
  expected: app.marker?.build,
851
851
  actual: apiMarker,
852
852
  status: readiness.ok && apiMarker === app.marker?.build ? 'pass' : 'fail',
@@ -7,7 +7,6 @@ const defaultReportPath =
7
7
  '.codex/reports/performance-readiness/ultramodern-performance-readiness.json';
8
8
  const configPath = 'scripts/ultramodern-performance-readiness.config.mjs';
9
9
  const compactConfigPath = '.modernjs/ultramodern.json';
10
- const legacyContractPath = '.modernjs/ultramodern-generated-contract.json';
11
10
  const optOutEnv = 'ULTRAMODERN_PERFORMANCE_READINESS_DIAGNOSTICS';
12
11
  const signalIds = [
13
12
  'bfcache',
@@ -219,15 +218,8 @@ const readGeneratedContractView = () => {
219
218
  };
220
219
  }
221
220
 
222
- if (exists(legacyContractPath)) {
223
- return {
224
- sourcePath: legacyContractPath,
225
- ...readJson(legacyContractPath),
226
- };
227
- }
228
-
229
221
  throw new Error(
230
- `Missing UltraModern config. Expected ${compactConfigPath} or ${legacyContractPath}.`,
222
+ `Missing UltraModern config. Expected ${compactConfigPath}.`,
231
223
  );
232
224
  };
233
225