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

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 (38) hide show
  1. package/bin/modern-api-check-files.mjs +4 -1
  2. package/bin/modern-api-check.mjs +1 -1
  3. package/bin/modern-i18n-check.mjs +4 -0
  4. package/dist/cjs/cli/microvertical-api-check.cjs +36 -7
  5. package/dist/cjs/cli/workspace-source-check.cjs +90 -17
  6. package/dist/cjs/microvertical-api-baseline.cjs +81 -244
  7. package/dist/cjs/microvertical-api-boundary.cjs +116 -38
  8. package/dist/cjs/microvertical-api-owner.cjs +5 -2
  9. package/dist/cjs/module-graph.cjs +1820 -0
  10. package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +1 -1
  11. package/dist/cjs/source-analysis.cjs +108 -0
  12. package/dist/cjs/strict-effect-runtime.cjs +16 -51
  13. package/dist/esm/cli/microvertical-api-check.js +26 -7
  14. package/dist/esm/cli/workspace-source-check.js +86 -13
  15. package/dist/esm/microvertical-api-baseline.js +66 -214
  16. package/dist/esm/microvertical-api-boundary.js +114 -39
  17. package/dist/esm/microvertical-api-owner.js +5 -2
  18. package/dist/esm/module-graph.js +1762 -0
  19. package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
  20. package/dist/esm/source-analysis.js +48 -0
  21. package/dist/esm/strict-effect-runtime.js +16 -50
  22. package/dist/esm-node/cli/microvertical-api-check.js +26 -7
  23. package/dist/esm-node/cli/workspace-source-check.js +86 -13
  24. package/dist/esm-node/microvertical-api-baseline.js +66 -214
  25. package/dist/esm-node/microvertical-api-boundary.js +114 -39
  26. package/dist/esm-node/microvertical-api-owner.js +5 -2
  27. package/dist/esm-node/module-graph.js +1763 -0
  28. package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
  29. package/dist/esm-node/source-analysis.js +49 -0
  30. package/dist/esm-node/strict-effect-runtime.js +16 -50
  31. package/dist/types/cli/microvertical-api-check.d.ts +1 -1
  32. package/dist/types/cli/workspace-source-check.d.ts +1 -1
  33. package/dist/types/microvertical-api-baseline.d.ts +0 -5
  34. package/dist/types/microvertical-api-boundary.d.ts +15 -1
  35. package/dist/types/microvertical-api-owner.d.ts +1 -1
  36. package/dist/types/module-graph.d.ts +92 -0
  37. package/dist/types/source-analysis.d.ts +13 -0
  38. package/package.json +17 -13
@@ -1,66 +1,14 @@
1
1
  import "node:module";
2
- import node_fs from "node:fs";
3
- import node_path from "node:path";
4
- import { parse } from "@babel/parser";
5
- import traverse, { Hub, NodePath } from "@babel/traverse";
6
2
  import { baselinePublicIdentityIsExact } from "./microvertical-api-owner.js";
3
+ import { createModuleGraph, exportedConst, localConst, propertyName } from "./module-graph.js";
4
+ import { SourceSyntaxError, unwrapExpression } from "./source-analysis.js";
7
5
  import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
8
- const consumerParserPlugins = (filePath)=>filePath.endsWith('.tsx') || filePath.endsWith('.jsx') ? [
9
- "typescript",
10
- 'jsx'
11
- ] : [
12
- "typescript"
13
- ];
14
- class ConsumerSyntaxError extends Error {
15
- }
16
- class ConsumerValidationHub extends Hub {
17
- buildError(_node, message) {
18
- return new ConsumerSyntaxError(message);
19
- }
20
- }
21
- function parseConsumer(filePath) {
22
- if (node_fs.statSync(filePath).size > 1000000) throw new Error(`${filePath}: consumer source exceeds 1 MB analysis budget`);
23
- let file;
24
- try {
25
- file = parse(node_fs.readFileSync(filePath, 'utf8'), {
26
- sourceType: 'module',
27
- sourceFilename: filePath,
28
- plugins: consumerParserPlugins(filePath)
29
- });
30
- } catch (error) {
31
- if ('object' == typeof error && null !== error && 'code' in error && 'BABEL_PARSER_SYNTAX_ERROR' === error.code) throw new ConsumerSyntaxError(error instanceof Error ? error.message : String(error));
32
- throw error;
33
- }
34
- const program = NodePath.get({
35
- hub: new ConsumerValidationHub(),
36
- parentPath: void 0,
37
- parent: file,
38
- container: file,
39
- key: 'program'
40
- });
41
- program.setContext();
42
- traverse(file, {
43
- AssignmentExpression (p) {
44
- throw new ConsumerSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
45
- },
46
- UpdateExpression (p) {
47
- throw new ConsumerSyntaxError(`contract bindings must be immutable at ${p.node.start}`);
48
- },
49
- TSModuleDeclaration () {
50
- throw new ConsumerSyntaxError('contract bindings must not be merged with namespaces');
51
- }
52
- }, program.scope, void 0, program);
53
- return file;
54
- }
55
6
  const camelCaseStem = (stem)=>stem.replaceAll(/-([a-z0-9])/gu, (_match, letter)=>letter.toUpperCase());
56
7
  const pascalCaseStem = (stem)=>{
57
8
  const camelStem = camelCaseStem(stem);
58
9
  return `${camelStem.slice(0, 1).toUpperCase()}${camelStem.slice(1)}`;
59
10
  };
60
11
  const identifierName = (node)=>void 0 !== node && __rspack_external__babel_types_6e8337dd.isIdentifier(node) ? node.name : void 0;
61
- const propertyName = (node)=>{
62
- if (void 0 !== node && (__rspack_external__babel_types_6e8337dd.isIdentifier(node) || __rspack_external__babel_types_6e8337dd.isStringLiteral(node) || __rspack_external__babel_types_6e8337dd.isNumericLiteral(node))) return __rspack_external__babel_types_6e8337dd.isIdentifier(node) ? node.name : String(node.value);
63
- };
64
12
  const accessPath = (node)=>{
65
13
  if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) return [
66
14
  node.name
@@ -74,11 +22,6 @@ const accessPath = (node)=>{
74
22
  }
75
23
  };
76
24
  const isAccessPath = (node, expected)=>accessPath(node)?.join('.') === expected.join('.');
77
- const unwrapExpression = (expression)=>{
78
- let current = expression;
79
- while(__rspack_external__babel_types_6e8337dd.isTSAsExpression(current) || __rspack_external__babel_types_6e8337dd.isParenthesizedExpression(current) || __rspack_external__babel_types_6e8337dd.isTSSatisfiesExpression(current))current = current.expression;
80
- return current;
81
- };
82
25
  const stringLiteral = (expression)=>{
83
26
  if (null == expression) return;
84
27
  const unwrapped = unwrapExpression(expression);
@@ -94,135 +37,6 @@ const callExpression = (expression, callee)=>{
94
37
  const unwrapped = unwrapExpression(expression);
95
38
  return __rspack_external__babel_types_6e8337dd.isCallExpression(unwrapped) && isAccessPath(unwrapped.callee, callee) ? unwrapped : void 0;
96
39
  };
97
- const localConst = (sourceFile, name)=>{
98
- for (const item of sourceFile.program.body){
99
- const statement = __rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(item) ? item.declaration : item;
100
- if (!__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement) || 'const' !== statement.kind) continue;
101
- const matches = statement.declarations.filter((declaration)=>__rspack_external__babel_types_6e8337dd.isIdentifier(declaration.id, {
102
- name
103
- }));
104
- if (1 === matches.length) return matches[0];
105
- }
106
- };
107
- const exportedConst = (sourceFile, name)=>{
108
- for (const statement of sourceFile.program.body)if (__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) && 'type' !== statement.exportKind) {
109
- if (__rspack_external__babel_types_6e8337dd.isVariableDeclaration(statement.declaration) && 'const' === statement.declaration.kind) {
110
- const declaration = statement.declaration.declarations.find((value)=>__rspack_external__babel_types_6e8337dd.isIdentifier(value.id, {
111
- name
112
- }));
113
- if (declaration) return declaration;
114
- }
115
- if (!statement.source && statement.specifiers.some((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && __rspack_external__babel_types_6e8337dd.isIdentifier(value.local, {
116
- name
117
- }) && __rspack_external__babel_types_6e8337dd.isIdentifier(value.exported, {
118
- name
119
- }))) return localConst(sourceFile, name);
120
- }
121
- };
122
- const resolveRelativeModulePath = (fromPath, specifier)=>{
123
- if (!/^\.\.?\//u.test(specifier)) return;
124
- const resolved = node_path.resolve(node_path.dirname(fromPath), specifier);
125
- const stem = resolved.replace(/\.(?:[cm]?[jt]sx?)$/u, '');
126
- for (const candidate of [
127
- `${stem}.ts`,
128
- `${stem}.tsx`,
129
- node_path.join(stem, 'index.ts'),
130
- node_path.join(stem, 'index.tsx')
131
- ])if (node_fs.existsSync(candidate) && node_fs.statSync(candidate).isFile()) return candidate;
132
- };
133
- const createModuleReader = (root)=>{
134
- const modules = new Map([
135
- [
136
- root.path,
137
- root
138
- ]
139
- ]);
140
- return (filePath)=>{
141
- if (modules.has(filePath)) return modules.get(filePath);
142
- if (modules.size >= 256) return;
143
- let module;
144
- try {
145
- module = {
146
- file: parseConsumer(filePath),
147
- path: filePath
148
- };
149
- } catch {
150
- module = void 0;
151
- }
152
- modules.set(filePath, module);
153
- return module;
154
- };
155
- };
156
- const importedBinding = (module, name)=>{
157
- for (const statement of module.file.program.body)if (__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) && 'type' !== statement.importKind) {
158
- for (const specifier of statement.specifiers)if (__rspack_external__babel_types_6e8337dd.isIdentifier(specifier.local, {
159
- name
160
- })) {
161
- if (__rspack_external__babel_types_6e8337dd.isImportSpecifier(specifier) && 'type' !== specifier.importKind) return {
162
- imported: propertyName(specifier.imported) ?? name,
163
- specifier: statement.source.value
164
- };
165
- if (__rspack_external__babel_types_6e8337dd.isImportDefaultSpecifier(specifier)) return {
166
- imported: 'default',
167
- specifier: statement.source.value
168
- };
169
- }
170
- }
171
- };
172
- const reexportedBinding = (module, name)=>{
173
- for (const statement of module.file.program.body){
174
- if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
175
- const specifier = statement.specifiers.find((value)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(value) && 'type' !== value.exportKind && propertyName(value.exported) === name);
176
- if (void 0 !== specifier && __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return {
177
- local: specifier.local.name,
178
- ...statement.source ? {
179
- specifier: statement.source.value
180
- } : {}
181
- };
182
- }
183
- };
184
- const defaultExport = (module)=>{
185
- for (const statement of module.file.program.body){
186
- if (!__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement)) continue;
187
- const declaration = statement.declaration;
188
- if (__rspack_external__babel_types_6e8337dd.isIdentifier(declaration)) return declaration.name;
189
- return __rspack_external__babel_types_6e8337dd.isExpression(declaration) ? declaration : void 0;
190
- }
191
- };
192
- const resolveBinding = (module, name, scope, readModule, seen)=>{
193
- const key = `${scope}:${module.path}#${name}`;
194
- if (seen.has(key) || seen.size >= 512) return;
195
- seen.add(key);
196
- const declaration = 'export' === scope ? exportedConst(module.file, name) : localConst(module.file, name);
197
- if (declaration?.init) return {
198
- expression: declaration.init,
199
- module
200
- };
201
- const throughModule = (specifier, imported)=>{
202
- const resolvedPath = resolveRelativeModulePath(module.path, specifier);
203
- const target = void 0 === resolvedPath ? void 0 : readModule(resolvedPath);
204
- return void 0 === target ? void 0 : resolveBinding(target, imported, 'export', readModule, seen);
205
- };
206
- if ('local' === scope) {
207
- const imported = importedBinding(module, name);
208
- return void 0 === imported ? void 0 : throughModule(imported.specifier, imported.imported);
209
- }
210
- if ('default' === name) {
211
- const exported = defaultExport(module);
212
- if ('string' == typeof exported) return resolveBinding(module, exported, 'local', readModule, seen);
213
- return void 0 === exported ? void 0 : {
214
- expression: exported,
215
- module
216
- };
217
- }
218
- const reexported = reexportedBinding(module, name);
219
- if (void 0 !== reexported) return void 0 === reexported.specifier ? resolveBinding(module, reexported.local, 'local', readModule, seen) : throughModule(reexported.specifier, reexported.local);
220
- for (const statement of module.file.program.body){
221
- if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) continue;
222
- const resolved = throughModule(statement.source.value, name);
223
- if (void 0 !== resolved) return resolved;
224
- }
225
- };
226
40
  const objectLiteral = (expression)=>{
227
41
  if (null == expression) return;
228
42
  const unwrapped = unwrapExpression(expression);
@@ -322,14 +136,21 @@ const singleAddedArgument = (expression, factory, names)=>{
322
136
  if (1 !== chain.base.arguments.length || !names.includes(stringLiteral(chain.base.arguments[0]) ?? '') || 1 !== chain.methods.length || method?.name !== 'add' || 1 !== method.arguments.length) return;
323
137
  return method.arguments[0];
324
138
  };
325
- const foundationIsExact = (declaration, stem, readinessSchemaName)=>{
326
- const group = singleAddedArgument(declaration?.init, [
139
+ const rootGroupExpression = (graph, expression)=>{
140
+ if (void 0 === expression) return;
141
+ const node = unwrapExpression(expression);
142
+ if (!__rspack_external__babel_types_6e8337dd.isIdentifier(node)) return node;
143
+ const resolved = graph.resolve(graph.root, node.name, 'local');
144
+ return resolved?.kind === 'declaration' && resolved.module === graph.root ? unwrapExpression(resolved.expression) : void 0;
145
+ };
146
+ const foundationGroup = (graph, declaration, stem, readinessSchemaName)=>{
147
+ const group = rootGroupExpression(graph, singleAddedArgument(declaration?.init, [
327
148
  'HttpApi',
328
149
  'make'
329
150
  ], [
330
151
  `${pascalCaseStem(stem)}FoundationApi`,
331
152
  `${pascalCaseStem(stem)}ApiFoundation`
332
- ]);
153
+ ]));
333
154
  const endpointExpression = singleAddedArgument(group, [
334
155
  'HttpApiGroup',
335
156
  'make'
@@ -342,17 +163,24 @@ const foundationIsExact = (declaration, stem, readinessSchemaName)=>{
342
163
  ], 3);
343
164
  const endpointOptions = objectLiteral(endpoint?.arguments[2]);
344
165
  const endpointProperties = void 0 === endpointOptions ? void 0 : propertyAssignments(endpointOptions.properties);
345
- return void 0 !== endpoint && 'readiness' === stringLiteral(endpoint.arguments[0]) && stringLiteral(endpoint.arguments[1]) === `/${stem}/readiness` && endpointProperties?.size === 1 && identifierName(endpointProperties.get('success')?.value) === readinessSchemaName;
166
+ return void 0 !== endpoint && 'readiness' === stringLiteral(endpoint.arguments[0]) && stringLiteral(endpoint.arguments[1]) === `/${stem}/readiness` && endpointProperties?.size === 1 && identifierName(endpointProperties.get('success')?.value) === readinessSchemaName ? group : void 0;
346
167
  };
347
- const rootComposesFoundation = (sourceFile, declaration, stem, foundationName)=>{
168
+ const rootComposesFoundation = (graph, declaration, stem, foundationName, foundation)=>{
169
+ const sourceFile = graph.root.file;
348
170
  const chain = directCallChain(declaration?.init);
349
171
  const first = chain?.methods[0];
172
+ const firstArgument = first?.arguments[0];
350
173
  return void 0 !== chain && isAccessPath(chain.base.callee, [
351
174
  'HttpApi',
352
175
  'make'
353
- ]) && 1 === chain.base.arguments.length && stringLiteral(chain.base.arguments[0]) === `${pascalCaseStem(stem)}Api` && chain.methods.length > 0 && chain.methods.every((method, index)=>1 === method.arguments.length && ('add' === method.name || 'addHttpApi' === method.name || index === chain.methods.length - 1 && 'pipe' === method.name && 'identity' === identifierName(method.arguments[0]) && importsExactBindings(sourceFile, 'effect', [
176
+ ]) && 1 === chain.base.arguments.length && stringLiteral(chain.base.arguments[0]) === `${pascalCaseStem(stem)}Api` && chain.methods.length > 0 && chain.methods.every((method, index)=>{
177
+ const combinatorArity = nonEndpointCombinators.api[method.name];
178
+ if (void 0 !== combinatorArity) return method.arguments.length === combinatorArity;
179
+ if ('add' === method.name) return method.arguments.length > 0;
180
+ return 1 === method.arguments.length && ('addHttpApi' === method.name || index === chain.methods.length - 1 && 'pipe' === method.name && 'identity' === identifierName(method.arguments[0]) && importsExactBindings(sourceFile, 'effect', [
354
181
  'identity'
355
- ]))) && first?.name === 'addHttpApi' && identifierName(first.arguments[0]) === foundationName;
182
+ ]));
183
+ }) && void 0 !== firstArgument && (first?.name === 'addHttpApi' && identifierName(firstArgument) === foundationName || first?.name === 'add' && __rspack_external__babel_types_6e8337dd.isIdentifier(firstArgument) && rootGroupExpression(graph, firstArgument) === foundation);
356
184
  };
357
185
  const nonEndpointCombinators = {
358
186
  api: {
@@ -368,8 +196,7 @@ const nonEndpointCombinators = {
368
196
  middleware: 1
369
197
  }
370
198
  };
371
- const reachableEndpoints = (rootModule, declaration)=>{
372
- const readModule = createModuleReader(rootModule);
199
+ const reachableEndpoints = (graph, declaration)=>{
373
200
  const active = new Set();
374
201
  const endpoints = [];
375
202
  const identities = new Set();
@@ -391,8 +218,8 @@ const reachableEndpoints = (rootModule, declaration)=>{
391
218
  active.add(node);
392
219
  try {
393
220
  if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
394
- const resolved = resolveBinding(module, node.name, 'local', readModule, new Set());
395
- return void 0 !== resolved && visit(resolved.expression, resolved.module, kind, group);
221
+ const resolved = graph.resolve(module, node.name, 'local');
222
+ return resolved?.kind === 'declaration' && visit(resolved.expression, resolved.module, kind, group);
396
223
  }
397
224
  if ('endpoint' === kind) {
398
225
  if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) || node.arguments.length < 2 || node.arguments.length > 3) return false;
@@ -436,18 +263,36 @@ const reachableEndpoints = (rootModule, declaration)=>{
436
263
  }
437
264
  const combinatorArity = combinators[method.name];
438
265
  if (void 0 !== combinatorArity) return method.arguments.length === combinatorArity;
266
+ if ('add' === method.name) return method.arguments.length > 0 && method.arguments.every((argument)=>visitAdded(argument, module, 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : ''));
439
267
  if (1 !== method.arguments.length) return false;
440
268
  if ('pipe' === method.name) return 'identity' === identifierName(method.arguments[0]) && importsExactBindings(module.file, 'effect', [
441
269
  'identity'
442
270
  ]);
443
- if ('add' === method.name) return visit(method.arguments[0], module, 'api' === kind ? 'group' : 'endpoint', 'group' === kind ? name : '');
444
271
  return 'api' === kind && 'addHttpApi' === method.name && visit(method.arguments[0], module, 'api');
445
272
  });
446
273
  } finally{
447
274
  active.delete(node);
448
275
  }
449
276
  };
450
- return visit(declaration?.init, rootModule, 'api') ? endpoints : void 0;
277
+ const visitAdded = (argument, module, kind, group)=>{
278
+ if (!__rspack_external__babel_types_6e8337dd.isSpreadElement(argument)) return visit(argument, module, kind, group);
279
+ let node = unwrapExpression(argument.argument);
280
+ let owner = module;
281
+ if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
282
+ const resolved = graph.resolve(module, node.name, 'local');
283
+ if (resolved?.kind !== 'declaration') return false;
284
+ node = unwrapExpression(resolved.expression);
285
+ owner = resolved.module;
286
+ }
287
+ if (!__rspack_external__babel_types_6e8337dd.isArrayExpression(node) || active.has(node) || ++visits > 2048) return false;
288
+ active.add(node);
289
+ try {
290
+ return node.elements.every((element)=>null !== element && visitAdded(element, owner, kind, group));
291
+ } finally{
292
+ active.delete(node);
293
+ }
294
+ };
295
+ return visit(declaration?.init, graph.root, 'api') ? endpoints : void 0;
451
296
  };
452
297
  const operationContextFields = (property)=>{
453
298
  const constructorCall = exactCall(property.value, [
@@ -578,8 +423,8 @@ const readinessSchemaIsShared = (declaration, markerSchemaName, ownerMarkerIsIde
578
423
  return void 0 !== schema && (schema.identity && ownerMarkerIsIdentity || 1 === schema.assignments.size && identifierName(schema.assignments.get('marker')?.value) === markerSchemaName);
579
424
  };
580
425
  const declarationIsIdentifier = (declaration, identifier)=>declaration?.init != null && identifierName(unwrapExpression(declaration.init)) === identifier;
581
- const validateParsedContract = (module, stem, expectation)=>{
582
- const sourceFile = module.file;
426
+ const validateParsedContract = (graph, stem, expectation)=>{
427
+ const sourceFile = graph.root.file;
583
428
  const exportStem = camelCaseStem(stem);
584
429
  const foundationName = `${exportStem}FoundationApi`;
585
430
  const markerSchemaName = `${exportStem}MarkerSchema`;
@@ -594,9 +439,10 @@ const validateParsedContract = (module, stem, expectation)=>{
594
439
  if (!importsSharedBaselinePrimitives(sourceFile, expectation.baselinePackage)) return 'MicroVertical root contract must import exact baseline primitives from the framework baseline package';
595
440
  if (!markerSchemaIsShared(sourceFile, markerDeclaration)) return 'MicroVertical root contract must consume the shared build marker schema without overriding shared fields';
596
441
  if (!readinessSchemaIsShared(exportedConst(sourceFile, readinessSchemaName), markerSchemaName, declarationIsIdentifier(markerDeclaration, 'MicroVerticalBuildMarkerSchema'))) return 'MicroVertical readiness schema must consume the shared readiness schema without overriding shared fields';
597
- if (!foundationIsExact(exportedConst(sourceFile, foundationName), stem, readinessSchemaName)) return 'MicroVertical readiness foundation API must directly compose its exact readiness endpoint and foundation identity';
598
- if (!rootComposesFoundation(sourceFile, exportedConst(sourceFile, `${exportStem}Api`), stem, foundationName)) return 'MicroVertical root API must explicitly compose its readiness foundation API';
599
- const endpoints = reachableEndpoints(module, exportedConst(sourceFile, `${exportStem}Api`));
442
+ const foundation = foundationGroup(graph, exportedConst(sourceFile, foundationName), stem, readinessSchemaName);
443
+ if (void 0 === foundation) return 'MicroVertical readiness foundation API must directly compose its exact readiness endpoint and foundation identity';
444
+ if (!rootComposesFoundation(graph, exportedConst(sourceFile, `${exportStem}Api`), stem, foundationName, foundation)) return 'MicroVertical root API must explicitly compose its readiness foundation API';
445
+ const endpoints = reachableEndpoints(graph, exportedConst(sourceFile, `${exportStem}Api`));
600
446
  if (!endpoints) return 'MicroVertical root API must compose bounded native endpoint declarations without ambiguous identities';
601
447
  if (!operationMapIsConnected(exportedConst(sourceFile, `${exportStem}OperationContexts`), stem, expectation.operationPaths ?? {}, endpoints)) return 'MicroVertical operation map must construct every operation with the shared context constructor';
602
448
  if (!metadataIsExact(exportedConst(sourceFile, `${exportStem}ApiContract`), expectation, endpoints)) return 'MicroVertical root contract must keep exact owner and API path metadata without forbidden fields';
@@ -608,15 +454,21 @@ const configuredMicroVerticalApiStem = (verticalPath, verticals)=>{
608
454
  };
609
455
  const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
610
456
  try {
611
- const sourceFile = parseConsumer(filePath);
612
- if (!baselinePublicIdentityIsExact(filePath, expectation)) return 'MicroVertical baseline imports must resolve the exact framework owner public export and schema identity';
613
- return validateParsedContract({
614
- file: sourceFile,
615
- path: filePath
616
- }, stem, expectation);
457
+ const modules = createModuleGraph();
458
+ const graph = {
459
+ ...modules,
460
+ root: modules.module(filePath)
461
+ };
462
+ const [mutation] = graph.root.mutations;
463
+ if (mutation) return `MicroVertical root contract must be valid TypeScript syntax (contract bindings must be immutable at ${mutation.start})`;
464
+ let identityMismatch;
465
+ if (!baselinePublicIdentityIsExact(filePath, expectation, (message)=>{
466
+ identityMismatch = message;
467
+ })) return `MicroVertical baseline imports must resolve the exact framework owner public export and schema identity${identityMismatch ? ` (${identityMismatch})` : ''}`;
468
+ return validateParsedContract(graph, stem, expectation);
617
469
  } catch (error) {
618
- if (error instanceof ConsumerSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
470
+ if (error instanceof SourceSyntaxError) return `MicroVertical root contract must be valid TypeScript syntax (${error.message})`;
619
471
  throw error;
620
472
  }
621
473
  };
622
- export { configuredMicroVerticalApiStem, consumerParserPlugins, microVerticalApiBaselineViolation };
474
+ export { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation };
@@ -2,8 +2,10 @@ import "node:module";
2
2
  import node_fs from "node:fs";
3
3
  import node_path from "node:path";
4
4
  import { parse } from "@babel/parser";
5
- import { configuredMicroVerticalApiStem, consumerParserPlugins, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
5
+ import { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
6
6
  import { baselinePackage, resolveBaselinePackageDirectory } from "./microvertical-api-owner.js";
7
+ import { createModuleGraph } from "./module-graph.js";
8
+ import { SourceSyntaxError, consumerParserPlugins } from "./source-analysis.js";
7
9
  import { createEffectApiImportResolver, strictEffectRuntimeTopologyViolation } from "./strict-effect-runtime.js";
8
10
  import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
9
11
  const ignored = new Set([
@@ -20,13 +22,42 @@ const normalize = (file)=>file.split(node_path.sep).join('/').replace(/^\.\//u,
20
22
  const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
21
23
  const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
22
24
  function configuredApps(root, supplied) {
23
- const source = supplied ?? JSON.parse(node_fs.readFileSync(node_path.join(root, '.modernjs/ultramodern.json'), 'utf8')).topology?.apps;
24
- if (!Array.isArray(source)) throw new Error('.modernjs/ultramodern.json: topology.apps must be an array');
25
+ const topology = supplied ? void 0 : JSON.parse(node_fs.readFileSync(node_path.join(root, 'topology/reference-topology.json'), 'utf8'));
26
+ if (topology && (!topology.shell || !Array.isArray(topology.verticals) || void 0 !== topology.shells && !Array.isArray(topology.shells))) throw new Error('topology/reference-topology.json: shell and verticals must be present');
27
+ const entries = topology?.shell ? [
28
+ topology.shell,
29
+ ...topology.shells ?? [],
30
+ ...topology.verticals ?? []
31
+ ] : void 0;
32
+ const source = supplied ?? entries?.map((entry)=>{
33
+ if (!entry || 'string' != typeof entry.path || !entry.path || node_path.isAbsolute(entry.path) || entry.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have an explicit workspace path');
34
+ const packageFile = node_path.join(root, entry.path, 'package.json');
35
+ const manifest = JSON.parse(node_fs.readFileSync(packageFile, 'utf8'));
36
+ if ('string' != typeof manifest.name || !manifest.name || void 0 !== entry.package && entry.package !== manifest.name) throw new Error(`${entry.path}/package.json: package name must match topology`);
37
+ const api = entry.api;
38
+ const stem = api?.stem ?? api?.readiness?.endpoint?.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)\/readiness$/u)?.[1];
39
+ return {
40
+ id: entry.id,
41
+ kind: entry.kind,
42
+ path: entry.path,
43
+ package: manifest.name,
44
+ surfaceProfile: entry.surfaceProfile,
45
+ deliveryUnitKind: entry.deliveryUnitKind,
46
+ ...void 0 === api ? {} : {
47
+ api: {
48
+ stem,
49
+ prefix: api.bff?.prefix,
50
+ protocol: api.protocol
51
+ }
52
+ }
53
+ };
54
+ });
55
+ if (!Array.isArray(source)) throw new Error('topology/reference-topology.json: shell and verticals must describe apps');
25
56
  const paths = new Set();
26
57
  for (const app of source){
27
- if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || node_path.isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('.modernjs/ultramodern.json: each app must have a relative workspace path');
58
+ if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || node_path.isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have a relative workspace path');
28
59
  const key = normalize(app.path);
29
- if (paths.has(key)) throw new Error(`.modernjs/ultramodern.json: duplicate app path ${key}`);
60
+ if (paths.has(key)) throw new Error(`topology/reference-topology.json: duplicate app path ${key}`);
30
61
  paths.add(key);
31
62
  for (const name of [
32
63
  'id',
@@ -35,7 +66,7 @@ function configuredApps(root, supplied) {
35
66
  'surfaceProfile',
36
67
  'deliveryUnitKind'
37
68
  ])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
38
- for (const [field, allowed] of Object.entries({
69
+ const allowedFields = {
39
70
  kind: [
40
71
  'shell',
41
72
  'vertical'
@@ -49,7 +80,11 @@ function configuredApps(root, supplied) {
49
80
  'microvertical',
50
81
  'horizontal-remote'
51
82
  ]
52
- }))if (void 0 !== app[field] && !allowed.includes(app[field])) throw new Error(`${key}: invalid ${field}`);
83
+ };
84
+ for (const field of Object.keys(allowedFields)){
85
+ const value = app[field];
86
+ if (void 0 !== value && !allowedFields[field].includes(value)) throw new Error(`${key}: invalid ${field}`);
87
+ }
53
88
  if (void 0 !== app.api) {
54
89
  if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
55
90
  if (void 0 !== app.api.protocol && ![
@@ -137,12 +172,27 @@ function check(options, topology) {
137
172
  `${base}/index.tsx`
138
173
  ].find((candidate)=>exists(candidate));
139
174
  };
140
- const composedModules = (file)=>{
175
+ const clientExportFile = (appPath, exported)=>{
176
+ if ('string' != typeof exported || !exported.startsWith('./') || exported.split(/[\\/]/u).includes('..') || !/\.[cm]?[jt]sx?$/u.test(exported)) return;
177
+ const appDirectory = absolute(appPath);
178
+ const candidate = node_path.resolve(appDirectory, exported);
179
+ try {
180
+ const workspace = node_fs.realpathSync(root);
181
+ const owner = node_fs.realpathSync(appDirectory);
182
+ const real = node_fs.realpathSync(candidate);
183
+ if (!owner.startsWith(`${workspace}${node_path.sep}`) || !real.startsWith(`${owner}${node_path.sep}`) || !node_fs.statSync(real).isFile()) return;
184
+ } catch {
185
+ return;
186
+ }
187
+ return normalize(node_path.relative(root, candidate));
188
+ };
189
+ const composedModules = (file, appPath)=>{
141
190
  const order = [];
142
191
  const seen = new Set();
143
192
  const queue = [
144
193
  file
145
194
  ];
195
+ const owner = node_fs.realpathSync(absolute(appPath));
146
196
  while(queue.length > 0 && order.length < 128){
147
197
  const current = queue.shift();
148
198
  if (seen.has(current)) continue;
@@ -153,16 +203,19 @@ function check(options, topology) {
153
203
  for (const statement of ast.program.body){
154
204
  const source = (__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement)) && statement.source ? statement.source.value : void 0;
155
205
  const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
156
- if (void 0 !== resolved && !seen.has(resolved)) queue.push(resolved);
206
+ if (void 0 !== resolved && !seen.has(resolved)) {
207
+ const real = node_fs.realpathSync(absolute(resolved));
208
+ if (real.startsWith(`${owner}${node_path.sep}`)) queue.push(resolved);
209
+ }
157
210
  }
158
211
  }
159
212
  return order;
160
213
  };
161
- const moduleShape = (file, imports, calls, patterns = [], defaultExport, composed = false)=>{
214
+ const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
162
215
  if (!exists(file)) return;
163
216
  const ast = parseFile(file);
164
217
  if (!ast) return;
165
- const shapeFiles = composed ? composedModules(file) : [
218
+ const shapeFiles = composedAppPath ? composedModules(file, composedAppPath) : [
166
219
  file
167
220
  ];
168
221
  const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
@@ -255,7 +308,6 @@ function check(options, topology) {
255
308
  if (violation) diagnostics.push(`${file}: ${violation}`);
256
309
  }
257
310
  });
258
- let owner;
259
311
  for (const appPath of appDirectories)guarded(appPath, ()=>{
260
312
  const app = byPath.get(appPath);
261
313
  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`);
@@ -272,19 +324,25 @@ function check(options, topology) {
272
324
  }
273
325
  return;
274
326
  }
275
- if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in .modernjs/ultramodern.json`);
327
+ if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in topology/reference-topology.json`);
276
328
  const stem = app?.api?.stem ?? node_path.posix.basename(appPath);
277
329
  const rpc = app?.api?.protocol === 'rpc';
278
330
  const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
279
- const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
280
331
  const entry = `${appPath}/api/index.ts`;
281
332
  for (const file of [
282
333
  entry,
283
- contract,
284
- client
334
+ contract
285
335
  ])assert(exists(file), `${file}: required API surface is missing`);
336
+ const packageFile = `${appPath}/package.json`;
337
+ assert(exists(packageFile), `${packageFile}: required API package is missing`);
338
+ if (!exists(packageFile)) return;
339
+ const manifest = JSON.parse(read(packageFile));
340
+ assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
341
+ const clientKey = `./api/${rpc ? 'rpc-client' : 'client'}`;
342
+ const client = clientExportFile(appPath, manifest.exports?.[clientKey]);
343
+ assert(void 0 !== client, `${packageFile}: invalid API client export`);
344
+ assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
286
345
  noPath(`${appPath}/shared/${rpc ? 'api' : 'rpc'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} contract`);
287
- noPath(`${appPath}/src/api/${stem}-${rpc ? 'client' : 'rpc-client'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} client`);
288
346
  if (rpc) {
289
347
  moduleShape(entry, [
290
348
  [
@@ -315,7 +373,7 @@ function check(options, topology) {
315
373
  ], 'apiRuntime');
316
374
  moduleShape(contract, [
317
375
  [
318
- 'effect/unstable/rpc',
376
+ 'effect/rpc',
319
377
  [
320
378
  'Rpc',
321
379
  'RpcGroup'
@@ -333,7 +391,7 @@ function check(options, topology) {
333
391
  'Schema.Struct'
334
392
  ]);
335
393
  } else if (exists(contract)) {
336
- owner ??= options.baselinePackageDirectory ?? resolveBaselinePackageDirectory(node_path.join(root, 'package.json'));
394
+ const owner = options.baselinePackageDirectory ?? resolveBaselinePackageDirectory(absolute(contract));
337
395
  const apiPrefix = app?.api?.prefix ?? `/${stem}-api`;
338
396
  const basePath = `${apiPrefix}/${stem}`;
339
397
  const expectation = {
@@ -350,21 +408,24 @@ function check(options, topology) {
350
408
  const violation = microVerticalApiBaselineViolation(stem, absolute(contract), expectation);
351
409
  if (violation) diagnostics.push(`${contract}: ${violation}`);
352
410
  }
353
- moduleShape(client, [
354
- [
355
- effect,
411
+ if (client) {
412
+ const relativeContract = node_path.posix.relative(node_path.posix.dirname(client), contract);
413
+ moduleShape(client, [
356
414
  [
357
- 'Effect',
358
- rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
415
+ effect,
416
+ [
417
+ 'Effect',
418
+ rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
419
+ ]
420
+ ],
421
+ [
422
+ relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
423
+ []
359
424
  ]
360
- ],
361
- [
362
- rpc ? '../../shared/rpc.ts' : '../../shared/api',
363
- []
364
- ]
365
- ], [
366
- rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
367
- ], [], void 0, true);
425
+ ], [
426
+ rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
427
+ ], [], void 0, appPath);
428
+ }
368
429
  moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
369
430
  [
370
431
  /\bbackendFederationContract\b/u,
@@ -405,12 +466,26 @@ function check(options, topology) {
405
466
  'must enable strictEffectApproach'
406
467
  ]
407
468
  ]);
408
- const packageFile = `${appPath}/package.json`;
409
- if (exists(packageFile)) {
410
- const manifest = JSON.parse(read(packageFile));
411
- assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
412
- assert(manifest.exports?.[`./api/${rpc ? 'rpc-client' : 'client'}`] === `./src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`, `${packageFile}: invalid API client export`);
413
- assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
469
+ });
470
+ const sourceRules = options.sourceRules ?? [];
471
+ const graph = createModuleGraph();
472
+ for (const file of sourceRules.length ? sourceFiles : [])if (/\.[cm]?[jt]sx?$/u.test(file)) guarded(file, ()=>{
473
+ let module;
474
+ try {
475
+ module = graph.module(absolute(file));
476
+ } catch (error) {
477
+ if (!(error instanceof SourceSyntaxError)) throw error;
478
+ diagnostics.push(`${file}: source rules need parseable source (${error.message})`);
479
+ return;
480
+ }
481
+ for (const rule of sourceRules){
482
+ const messages = rule({
483
+ file,
484
+ module,
485
+ graph
486
+ });
487
+ if (!Array.isArray(messages) || !messages.every((message)=>'string' == typeof message)) throw new Error(`rule ${rule.name || 'anonymous'} must return an array of violation messages (return [] when the file passes)`);
488
+ for (const message of messages)diagnostics.push(`${file}: ${message}`);
414
489
  }
415
490
  });
416
491
  const shell = 'apps/shell-super-app';
@@ -447,4 +522,4 @@ function check(options, topology) {
447
522
  }
448
523
  const checkMicroVerticalApiBoundaries = (options)=>check(options, true);
449
524
  const checkMicroVerticalApiConsumerFiles = (options)=>check(options, false);
450
- export { checkMicroVerticalApiBoundaries, checkMicroVerticalApiConsumerFiles, configuredMicroVerticalApiStem, microVerticalApiBaselineViolation };
525
+ export { checkMicroVerticalApiBoundaries, checkMicroVerticalApiConsumerFiles, configuredMicroVerticalApiStem, createModuleGraph, microVerticalApiBaselineViolation };