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