@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.1 → 3.9.0-ultramodern.10

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 (29) hide show
  1. package/bin/modern-api-check-files.mjs +4 -0
  2. package/bin/modern-api-check.mjs +4 -0
  3. package/dist/cjs/cli/microvertical-api-check.cjs +71 -0
  4. package/dist/cjs/cli/oxlint.cjs +9 -5
  5. package/dist/cjs/microvertical-api-baseline.cjs +677 -0
  6. package/dist/cjs/microvertical-api-boundary.cjs +507 -0
  7. package/dist/cjs/microvertical-api-owner.cjs +217 -0
  8. package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +7 -5
  9. package/dist/cjs/strict-effect-runtime.cjs +411 -0
  10. package/dist/esm/cli/microvertical-api-check.js +33 -0
  11. package/dist/esm/cli/oxlint.js +10 -6
  12. package/dist/esm/microvertical-api-baseline.js +621 -0
  13. package/dist/esm/microvertical-api-boundary.js +449 -0
  14. package/dist/esm/microvertical-api-owner.js +162 -0
  15. package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +7 -5
  16. package/dist/esm/strict-effect-runtime.js +358 -0
  17. package/dist/esm-node/cli/microvertical-api-check.js +34 -0
  18. package/dist/esm-node/cli/oxlint.js +10 -6
  19. package/dist/esm-node/microvertical-api-baseline.js +622 -0
  20. package/dist/esm-node/microvertical-api-boundary.js +450 -0
  21. package/dist/esm-node/microvertical-api-owner.js +163 -0
  22. package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +7 -5
  23. package/dist/esm-node/strict-effect-runtime.js +359 -0
  24. package/dist/types/cli/microvertical-api-check.d.ts +1 -0
  25. package/dist/types/microvertical-api-baseline.d.ts +28 -0
  26. package/dist/types/microvertical-api-boundary.d.ts +32 -0
  27. package/dist/types/microvertical-api-owner.d.ts +6 -0
  28. package/dist/types/strict-effect-runtime.d.ts +10 -0
  29. package/package.json +25 -4
@@ -0,0 +1,507 @@
1
+ "use strict";
2
+ var __webpack_require__ = {};
3
+ (()=>{
4
+ __webpack_require__.n = (module)=>{
5
+ var getter = module && module.__esModule ? ()=>module['default'] : ()=>module;
6
+ __webpack_require__.d(getter, {
7
+ a: getter
8
+ });
9
+ return getter;
10
+ };
11
+ })();
12
+ (()=>{
13
+ __webpack_require__.d = (exports1, getters, values)=>{
14
+ var define = (defs, kind)=>{
15
+ for(var key in defs)if (__webpack_require__.o(defs, key) && !__webpack_require__.o(exports1, key)) Object.defineProperty(exports1, key, {
16
+ enumerable: true,
17
+ [kind]: defs[key]
18
+ });
19
+ };
20
+ define(getters, "get");
21
+ define(values, "value");
22
+ };
23
+ })();
24
+ (()=>{
25
+ __webpack_require__.o = (obj, prop)=>Object.prototype.hasOwnProperty.call(obj, prop);
26
+ })();
27
+ (()=>{
28
+ __webpack_require__.r = (exports1)=>{
29
+ if ("u" > typeof Symbol && Symbol.toStringTag) Object.defineProperty(exports1, Symbol.toStringTag, {
30
+ value: 'Module'
31
+ });
32
+ Object.defineProperty(exports1, '__esModule', {
33
+ value: true
34
+ });
35
+ };
36
+ })();
37
+ var __webpack_exports__ = {};
38
+ __webpack_require__.r(__webpack_exports__);
39
+ __webpack_require__.d(__webpack_exports__, {
40
+ checkMicroVerticalApiBoundaries: ()=>checkMicroVerticalApiBoundaries,
41
+ checkMicroVerticalApiConsumerFiles: ()=>checkMicroVerticalApiConsumerFiles,
42
+ configuredMicroVerticalApiStem: ()=>external_microvertical_api_baseline_cjs_namespaceObject.configuredMicroVerticalApiStem,
43
+ microVerticalApiBaselineViolation: ()=>external_microvertical_api_baseline_cjs_namespaceObject.microVerticalApiBaselineViolation
44
+ });
45
+ const external_node_fs_namespaceObject = require("node:fs");
46
+ var external_node_fs_default = /*#__PURE__*/ __webpack_require__.n(external_node_fs_namespaceObject);
47
+ const external_node_path_namespaceObject = require("node:path");
48
+ var external_node_path_default = /*#__PURE__*/ __webpack_require__.n(external_node_path_namespaceObject);
49
+ const parser_namespaceObject = require("@babel/parser");
50
+ const types_namespaceObject = require("@babel/types");
51
+ const external_microvertical_api_baseline_cjs_namespaceObject = require("./microvertical-api-baseline.cjs");
52
+ const external_microvertical_api_owner_cjs_namespaceObject = require("./microvertical-api-owner.cjs");
53
+ const external_strict_effect_runtime_cjs_namespaceObject = require("./strict-effect-runtime.cjs");
54
+ const ignored = new Set([
55
+ '.git',
56
+ '.modern',
57
+ '.modernjs',
58
+ '.output',
59
+ 'coverage',
60
+ 'dist',
61
+ 'node_modules',
62
+ 'repos'
63
+ ]);
64
+ const normalize = (file)=>file.split(external_node_path_default().sep).join('/').replace(/^\.\//u, '');
65
+ const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
66
+ const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
67
+ function configuredApps(root, supplied) {
68
+ const source = supplied ?? JSON.parse(external_node_fs_default().readFileSync(external_node_path_default().join(root, '.modernjs/ultramodern.json'), 'utf8')).topology?.apps;
69
+ if (!Array.isArray(source)) throw new Error('.modernjs/ultramodern.json: topology.apps must be an array');
70
+ const paths = new Set();
71
+ for (const app of source){
72
+ if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || external_node_path_default().isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('.modernjs/ultramodern.json: each app must have a relative workspace path');
73
+ const key = normalize(app.path);
74
+ if (paths.has(key)) throw new Error(`.modernjs/ultramodern.json: duplicate app path ${key}`);
75
+ paths.add(key);
76
+ for (const name of [
77
+ 'id',
78
+ 'kind',
79
+ 'package',
80
+ 'surfaceProfile',
81
+ 'deliveryUnitKind'
82
+ ])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
83
+ for (const [field, allowed] of Object.entries({
84
+ kind: [
85
+ 'shell',
86
+ 'vertical'
87
+ ],
88
+ surfaceProfile: [
89
+ 'full-stack',
90
+ 'api-only',
91
+ 'ui-only'
92
+ ],
93
+ deliveryUnitKind: [
94
+ 'microvertical',
95
+ 'horizontal-remote'
96
+ ]
97
+ }))if (void 0 !== app[field] && !allowed.includes(app[field])) throw new Error(`${key}: invalid ${field}`);
98
+ if (void 0 !== app.api) {
99
+ if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
100
+ if (void 0 !== app.api.protocol && ![
101
+ 'rest',
102
+ 'rpc'
103
+ ].includes(app.api.protocol)) throw new Error(`${key}: api.protocol must be rest or rpc`);
104
+ if (void 0 !== app.api.stem && ('string' != typeof app.api.stem || !/^[a-z0-9]+(?:-[a-z0-9]+)*$/u.test(app.api.stem))) throw new Error(`${key}: invalid api.stem`);
105
+ if (void 0 !== app.api.prefix && ('string' != typeof app.api.prefix || !/^\/[^\s?#]*$/u.test(app.api.prefix))) throw new Error(`${key}: invalid api.prefix`);
106
+ for (const field of [
107
+ 'additionalPaths',
108
+ 'operationPaths'
109
+ ]){
110
+ const value = app.api[field];
111
+ if (void 0 !== value && (!value || 'object' != typeof value || Array.isArray(value) || Object.values(value).some((entry)=>'string' != typeof entry))) throw new Error(`${key}: api.${field} must map names to paths`);
112
+ }
113
+ }
114
+ }
115
+ return source;
116
+ }
117
+ function check(options, topology) {
118
+ const diagnostics = [];
119
+ const toolErrors = [];
120
+ let topologyFilesAnalyzed = 0;
121
+ const root = external_node_path_default().resolve(options.workspaceRoot);
122
+ const absolute = (file)=>external_node_path_default().join(root, file);
123
+ const exists = (file)=>external_node_fs_default().existsSync(absolute(file));
124
+ const read = (file)=>external_node_fs_default().readFileSync(absolute(file), 'utf8');
125
+ const assert = (value, message)=>{
126
+ if (!value) diagnostics.push(message);
127
+ };
128
+ const noPath = (file, reason)=>assert(!exists(file), `${file}: ${reason}`);
129
+ const guarded = (file, work)=>{
130
+ try {
131
+ work();
132
+ } catch (error) {
133
+ toolErrors.push(`${file}: ${errorMessage(error)}`);
134
+ }
135
+ };
136
+ try {
137
+ const apps = configuredApps(root, options.configuredApps);
138
+ const byPath = new Map(apps.map((app)=>[
139
+ normalize(app.path),
140
+ app
141
+ ]));
142
+ const directories = (base)=>exists(base) ? external_node_fs_default().readdirSync(absolute(base), {
143
+ withFileTypes: true
144
+ }).filter((entry)=>entry.isDirectory() && !ignored.has(entry.name)).map((entry)=>`${base}/${entry.name}`) : [];
145
+ const walk = (directory)=>exists(directory) ? external_node_fs_default().readdirSync(absolute(directory), {
146
+ withFileTypes: true
147
+ }).flatMap((entry)=>{
148
+ if (ignored.has(entry.name) || 'dist-cloudflare' === entry.name && byPath.has(directory)) return [];
149
+ const file = `${directory}/${entry.name}`;
150
+ return entry.isDirectory() ? walk(file) : entry.isFile() ? [
151
+ file
152
+ ] : [];
153
+ }) : [];
154
+ const parsed = new Map();
155
+ const parseFile = (file)=>{
156
+ if (parsed.has(file)) return parsed.get(file);
157
+ if (external_node_fs_default().statSync(absolute(file)).size > 1000000) throw new Error('consumer source exceeds 1 MB analysis budget');
158
+ try {
159
+ const ast = (0, parser_namespaceObject.parse)(read(file), {
160
+ sourceType: 'module',
161
+ sourceFilename: file,
162
+ plugins: (0, external_microvertical_api_baseline_cjs_namespaceObject.consumerParserPlugins)(file)
163
+ });
164
+ parsed.set(file, ast);
165
+ return ast;
166
+ } catch (error) {
167
+ if ('object' == typeof error && null !== error && 'code' in error && 'BABEL_PARSER_SYNTAX_ERROR' === error.code) {
168
+ diagnostics.push(`${file}: invalid source syntax (${errorMessage(error)})`);
169
+ parsed.set(file, void 0);
170
+ return;
171
+ }
172
+ throw error;
173
+ }
174
+ };
175
+ const resolveRelative = (fromFile, specifier)=>{
176
+ if (!/^\.\.?\//u.test(specifier)) return;
177
+ const base = external_node_path_default().posix.join(external_node_path_default().posix.dirname(fromFile), specifier).replace(/\.(?:[cm]?[jt]sx?)$/u, '');
178
+ return [
179
+ `${base}.ts`,
180
+ `${base}.tsx`,
181
+ `${base}/index.ts`,
182
+ `${base}/index.tsx`
183
+ ].find((candidate)=>exists(candidate));
184
+ };
185
+ const composedModules = (file)=>{
186
+ const order = [];
187
+ const seen = new Set();
188
+ const queue = [
189
+ file
190
+ ];
191
+ while(queue.length > 0 && order.length < 128){
192
+ const current = queue.shift();
193
+ if (seen.has(current)) continue;
194
+ seen.add(current);
195
+ order.push(current);
196
+ const ast = parseFile(current);
197
+ if (!ast) continue;
198
+ for (const statement of ast.program.body){
199
+ const source = (types_namespaceObject.isImportDeclaration(statement) || types_namespaceObject.isExportNamedDeclaration(statement) || types_namespaceObject.isExportAllDeclaration(statement)) && statement.source ? statement.source.value : void 0;
200
+ const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
201
+ if (void 0 !== resolved && !seen.has(resolved)) queue.push(resolved);
202
+ }
203
+ }
204
+ return order;
205
+ };
206
+ const moduleShape = (file, imports, calls, patterns = [], defaultExport, composed = false)=>{
207
+ if (!exists(file)) return;
208
+ const ast = parseFile(file);
209
+ if (!ast) return;
210
+ const shapeFiles = composed ? composedModules(file) : [
211
+ file
212
+ ];
213
+ const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
214
+ for (const [specifier, names] of imports){
215
+ const target = resolveRelative(file, specifier);
216
+ const matchesSpecifier = (item, from)=>void 0 === target ? item.source.value === specifier : resolveRelative(from, item.source.value) === target;
217
+ const imported = shapeFiles.flatMap((shapeFile)=>(parseFile(shapeFile)?.program.body ?? []).filter((item)=>types_namespaceObject.isImportDeclaration(item) && 'type' !== item.importKind && matchesSpecifier(item, shapeFile)));
218
+ assert(imported.length, `${file}: must import from ${specifier}`);
219
+ for (const name of names)assert(imported.some((item)=>types_namespaceObject.isImportDeclaration(item) && item.specifiers.some((value)=>types_namespaceObject.isImportSpecifier(value) && 'type' !== value.importKind && types_namespaceObject.isIdentifier(value.imported, {
220
+ name
221
+ }) && types_namespaceObject.isIdentifier(value.local, {
222
+ name
223
+ }))), `${file}: must import ${name} from ${specifier}`);
224
+ }
225
+ const found = new Set();
226
+ const collectCalls = (node)=>{
227
+ if (!types_namespaceObject.isCallExpression(node)) return;
228
+ if (types_namespaceObject.isIdentifier(node.callee)) found.add(node.callee.name);
229
+ if (types_namespaceObject.isMemberExpression(node.callee) && !node.callee.computed && types_namespaceObject.isIdentifier(node.callee.object) && types_namespaceObject.isIdentifier(node.callee.property)) found.add(`${node.callee.object.name}.${node.callee.property.name}`);
230
+ };
231
+ for (const shapeFile of shapeFiles){
232
+ const shapeAst = parseFile(shapeFile);
233
+ if (shapeAst) types_namespaceObject.traverseFast(shapeAst, collectCalls);
234
+ }
235
+ for (const call of calls)assert(found.has(call), `${file}: must call ${call}(...)`);
236
+ const source = read(file).replace(/\/\*[\s\S]*?\*\/|\/\/[^\n]*/gu, '');
237
+ for (const [pattern, reason] of patterns)assert(pattern.test(source), `${file}: ${reason}`);
238
+ if (defaultExport) assert(body.some((item)=>types_namespaceObject.isExportDefaultDeclaration(item) && types_namespaceObject.isIdentifier(item.declaration, {
239
+ name: defaultExport
240
+ })), `${file}: must default-export ${defaultExport}`);
241
+ };
242
+ const effect = '@modern-js/bff-effect/effect-client';
243
+ const appDirectories = [
244
+ ...new Set([
245
+ ...directories('apps'),
246
+ ...directories('verticals'),
247
+ ...byPath.keys()
248
+ ])
249
+ ];
250
+ for (const appPath of appDirectories)for (const suffix of [
251
+ 'api/effect',
252
+ 'api/lambda',
253
+ 'shared/effect',
254
+ 'src/effect'
255
+ ])noPath(`${appPath}/${suffix}`, 'legacy nested Effect paths are forbidden; use api/index.ts, shared/api.ts and src/api/*');
256
+ const sourceFiles = [
257
+ ...new Set([
258
+ 'apps',
259
+ 'verticals',
260
+ 'packages',
261
+ ...byPath.keys()
262
+ ].flatMap(walk))
263
+ ].filter((file)=>/\.(?:[cm]?[jt]sx?|json|md)$/u.test(file));
264
+ for (const file of sourceFiles)guarded(file, ()=>{
265
+ const source = read(file);
266
+ const patterns = [
267
+ [
268
+ /@modern-js\/plugin-bff\/(?:server|hono-server)(?![\w-])/u,
269
+ 'use Effect HttpApi instead of Hono helpers'
270
+ ],
271
+ [
272
+ /\bruntimeFramework\s*(?::|=)\s*['"]hono['"]/u,
273
+ 'must use the Effect runtime'
274
+ ],
275
+ [
276
+ /\bstrictEffectApproach\s*(?::|=)\s*false\b/u,
277
+ 'must keep strictEffectApproach enabled'
278
+ ]
279
+ ];
280
+ if (/\/api\//u.test(file)) patterns.push([
281
+ /\bnew\s+Response\s*\(|\bResponse\.json\s*\(/u,
282
+ 'must not hand-build Response objects'
283
+ ], [
284
+ /\b(?:request|req)\.(?:json|text|formData|arrayBuffer)\s*\(/u,
285
+ 'must use endpoint payload/query/params schemas instead of parsing request bodies'
286
+ ], [
287
+ /\bexport\s+const\s+handler\b|\bexport\s+default\s+async\b/u,
288
+ 'must not export raw request handlers'
289
+ ], [
290
+ /\bcreateHandler\s*[:=]\s*(?!defineEffectBff\b)/u,
291
+ 'must use defineEffectBff instead of unbranded handler factories'
292
+ ], [
293
+ /\bSchema\.(?:UnknownFromJsonString|Unknown|Any)\b/u,
294
+ 'must use concrete request, response and error schemas'
295
+ ]);
296
+ for (const [pattern, reason] of patterns)assert(!pattern.test(source), `${file}: ${reason}`);
297
+ if (topology && /\/api\/index\.ts$/u.test(file)) {
298
+ topologyFilesAnalyzed += 1;
299
+ const violation = (0, external_strict_effect_runtime_cjs_namespaceObject.strictEffectRuntimeTopologyViolation)(source, (0, external_strict_effect_runtime_cjs_namespaceObject.createEffectApiImportResolver)(absolute(file)));
300
+ if (violation) diagnostics.push(`${file}: ${violation}`);
301
+ }
302
+ });
303
+ let owner;
304
+ for (const appPath of appDirectories)guarded(appPath, ()=>{
305
+ const app = byPath.get(appPath);
306
+ const emits = app ? apiApp(app) || 'vertical' !== app.kind && void 0 !== app.api : appPath.startsWith('verticals/') || exists(`${appPath}/api/index.ts`) || exists(`${appPath}/shared/api.ts`);
307
+ if (!emits) {
308
+ if (app?.kind === 'vertical') {
309
+ assert(void 0 === app.api, `${appPath}: UI/horizontal unit must not declare an API`);
310
+ for (const suffix of [
311
+ 'api/index.ts',
312
+ 'api/effect-api.ts',
313
+ 'shared/api.ts',
314
+ 'shared/rpc.ts',
315
+ 'src/api'
316
+ ])noPath(`${appPath}/${suffix}`, 'unit has no API surface');
317
+ }
318
+ return;
319
+ }
320
+ if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in .modernjs/ultramodern.json`);
321
+ const stem = app?.api?.stem ?? external_node_path_default().posix.basename(appPath);
322
+ const rpc = app?.api?.protocol === 'rpc';
323
+ const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
324
+ const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
325
+ const entry = `${appPath}/api/index.ts`;
326
+ for (const file of [
327
+ entry,
328
+ contract,
329
+ client
330
+ ])assert(exists(file), `${file}: required API surface is missing`);
331
+ noPath(`${appPath}/shared/${rpc ? 'api' : 'rpc'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} contract`);
332
+ noPath(`${appPath}/src/api/${stem}-${rpc ? 'client' : 'rpc-client'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} client`);
333
+ if (rpc) {
334
+ moduleShape(entry, [
335
+ [
336
+ '@modern-js/bff-effect/effect-edge',
337
+ [
338
+ 'defineEffectBff',
339
+ 'Effect',
340
+ 'HttpApi',
341
+ 'Layer'
342
+ ]
343
+ ],
344
+ [
345
+ '../shared/rpc.ts',
346
+ []
347
+ ]
348
+ ], [
349
+ 'defineEffectBff',
350
+ 'HttpApi.make'
351
+ ], [
352
+ [
353
+ /\bconst\s+\w+RpcLayer\s*=\s*\w+RpcGroup\s*\.\s*toLayer\s*\(/u,
354
+ 'must implement RpcGroup.toLayer(...)'
355
+ ],
356
+ [
357
+ /\brpc\s*:\s*\{[\s\S]*?\bgroup\s*:[\s\S]*?\blayer\s*:[\s\S]*?\bpath\s*:[\s\S]*?\bserialization\s*:/u,
358
+ 'must register RPC group, layer, path and serialization'
359
+ ]
360
+ ], 'apiRuntime');
361
+ moduleShape(contract, [
362
+ [
363
+ 'effect/unstable/rpc',
364
+ [
365
+ 'Rpc',
366
+ 'RpcGroup'
367
+ ]
368
+ ],
369
+ [
370
+ effect,
371
+ [
372
+ 'Schema'
373
+ ]
374
+ ]
375
+ ], [
376
+ 'RpcGroup.make',
377
+ 'Rpc.make',
378
+ 'Schema.Struct'
379
+ ]);
380
+ } else if (exists(contract)) {
381
+ owner ??= options.baselinePackageDirectory ?? (0, external_microvertical_api_owner_cjs_namespaceObject.resolveBaselinePackageDirectory)(external_node_path_default().join(root, 'package.json'));
382
+ const apiPrefix = app?.api?.prefix ?? `/${stem}-api`;
383
+ const basePath = `${apiPrefix}/${stem}`;
384
+ const expectation = {
385
+ operationPaths: app?.api?.operationPaths,
386
+ additionalPaths: app?.api?.additionalPaths ?? {},
387
+ apiPrefix,
388
+ basePath,
389
+ effectClientPackage: effect,
390
+ ownerId: app?.id ?? external_node_path_default().posix.basename(appPath),
391
+ readinessPath: `${basePath}/readiness`,
392
+ baselinePackage: external_microvertical_api_owner_cjs_namespaceObject.baselinePackage,
393
+ baselinePackageDirectory: owner
394
+ };
395
+ const violation = (0, external_microvertical_api_baseline_cjs_namespaceObject.microVerticalApiBaselineViolation)(stem, absolute(contract), expectation);
396
+ if (violation) diagnostics.push(`${contract}: ${violation}`);
397
+ }
398
+ moduleShape(client, [
399
+ [
400
+ effect,
401
+ [
402
+ 'Effect',
403
+ rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
404
+ ]
405
+ ],
406
+ [
407
+ rpc ? '../../shared/rpc.ts' : '../../shared/api',
408
+ []
409
+ ]
410
+ ], [
411
+ rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
412
+ ], [], void 0, true);
413
+ moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
414
+ [
415
+ /\bbackendFederationContract\b/u,
416
+ 'must export backendFederationContract metadata'
417
+ ],
418
+ [
419
+ /role:\s*['"]microvertical-server['"]/u,
420
+ 'must describe the MicroVertical server role'
421
+ ],
422
+ [
423
+ /strictEffectApproach:\s*true/u,
424
+ 'must preserve strict Effect backend execution'
425
+ ],
426
+ [
427
+ /contractVersion:\s*['"]microvertical-server-effect-v1['"]/u,
428
+ 'must preserve the server contract version'
429
+ ],
430
+ [
431
+ /export\s*\{\s*default\s*,\s*default\s+as\s+runtime\s*\}\s+from\s+['"]\.\/index\.ts['"]/u,
432
+ 'must re-export the runtime as default and runtime'
433
+ ],
434
+ [
435
+ /^(?![\s\S]*\b(request|handler)\s*:\s*async\s*\()/u,
436
+ 'must not expose raw request handlers'
437
+ ]
438
+ ]);
439
+ moduleShape(`${appPath}/modern.config.ts`, [], [], [
440
+ [
441
+ /runtimeFramework:\s*['"]effect['"]/u,
442
+ 'must use bff.runtimeFramework: effect'
443
+ ],
444
+ [
445
+ /entry:\s*['"]\.\/api\/index['"]/u,
446
+ 'must use ./api/index'
447
+ ],
448
+ [
449
+ /strictEffectApproach:\s*true/u,
450
+ 'must enable strictEffectApproach'
451
+ ]
452
+ ]);
453
+ const packageFile = `${appPath}/package.json`;
454
+ if (exists(packageFile)) {
455
+ const manifest = JSON.parse(read(packageFile));
456
+ assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
457
+ assert(manifest.exports?.[`./api/${rpc ? 'rpc-client' : 'client'}`] === `./src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`, `${packageFile}: invalid API client export`);
458
+ assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
459
+ }
460
+ });
461
+ const shell = 'apps/shell-super-app';
462
+ const verticals = apps.filter((app)=>app.path.startsWith('verticals/') && apiApp(app));
463
+ if (exists(shell) && verticals.length) {
464
+ const file = `${shell}/src/api/vertical-clients.ts`;
465
+ assert(exists(file), `${file}: must aggregate vertical API clients`);
466
+ if (exists(file)) guarded(file, ()=>{
467
+ const ast = parseFile(file);
468
+ for (const app of verticals){
469
+ const specifier = `${app.package}/api/${app.api?.protocol === 'rpc' ? 'rpc-client' : 'client'}`;
470
+ assert(ast?.program.body.some((item)=>types_namespaceObject.isExportNamedDeclaration(item) && item.source?.value === specifier), `${file}: must re-export ${specifier}`);
471
+ }
472
+ });
473
+ }
474
+ if (exists(`${shell}/package.json`)) guarded(`${shell}/package.json`, ()=>assert(JSON.parse(read(`${shell}/package.json`)).exports?.['./api/clients'] === './src/api/vertical-clients.ts', `${shell}/package.json: must export ./api/clients`));
475
+ if (exists('topology/reference-topology.json')) guarded('topology/reference-topology.json', ()=>{
476
+ for (const vertical of JSON.parse(read('topology/reference-topology.json')).verticals ?? []){
477
+ if (vertical.api?.runtime === 'effect') {
478
+ assert(vertical.api.bff?.strictEffectApproach === true, `${vertical.id}: topology must mark strictEffectApproach true`);
479
+ assert('string' == typeof vertical.api.serverEntry && vertical.api.serverEntry.endsWith('/api/index.ts'), `${vertical.id}: topology must use api/index.ts`);
480
+ }
481
+ assert(!vertical.api?.effect, `${vertical.id}: topology must describe API directly, not api.effect`);
482
+ }
483
+ });
484
+ } catch (error) {
485
+ toolErrors.push(errorMessage(error));
486
+ }
487
+ return {
488
+ diagnostics,
489
+ toolErrors,
490
+ topologyFilesAnalyzed
491
+ };
492
+ }
493
+ const checkMicroVerticalApiBoundaries = (options)=>check(options, true);
494
+ const checkMicroVerticalApiConsumerFiles = (options)=>check(options, false);
495
+ exports.checkMicroVerticalApiBoundaries = __webpack_exports__.checkMicroVerticalApiBoundaries;
496
+ exports.checkMicroVerticalApiConsumerFiles = __webpack_exports__.checkMicroVerticalApiConsumerFiles;
497
+ exports.configuredMicroVerticalApiStem = __webpack_exports__.configuredMicroVerticalApiStem;
498
+ exports.microVerticalApiBaselineViolation = __webpack_exports__.microVerticalApiBaselineViolation;
499
+ for(var __rspack_i in __webpack_exports__)if (-1 === [
500
+ "checkMicroVerticalApiBoundaries",
501
+ "checkMicroVerticalApiConsumerFiles",
502
+ "configuredMicroVerticalApiStem",
503
+ "microVerticalApiBaselineViolation"
504
+ ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
505
+ Object.defineProperty(exports, '__esModule', {
506
+ value: true
507
+ });