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

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.
@@ -5,7 +5,7 @@ import { parse } from "@babel/parser";
5
5
  import { configuredMicroVerticalApiStem, microVerticalApiBaselineViolation } from "./microvertical-api-baseline.js";
6
6
  import { baselinePackage, resolveBaselinePackageDirectory } from "./microvertical-api-owner.js";
7
7
  import { createModuleGraph } from "./module-graph.js";
8
- import { SourceSyntaxError, consumerParserPlugins } from "./source-analysis.js";
8
+ import { SourceSyntaxError, consumerParserPlugins, unwrapExpression } from "./source-analysis.js";
9
9
  import { createEffectApiImportResolver, strictEffectRuntimeTopologyViolation } from "./strict-effect-runtime.js";
10
10
  import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
11
11
  const ignored = new Set([
@@ -18,6 +18,8 @@ const ignored = new Set([
18
18
  'node_modules',
19
19
  'repos'
20
20
  ]);
21
+ const MAX_CLOSURE_DEPTH = 128;
22
+ const MAX_CLOSURE_MODULES = 2048;
21
23
  const normalize = (file)=>file.split(node_path.sep).join('/').replace(/^\.\//u, '');
22
24
  const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
23
25
  const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
@@ -83,7 +85,7 @@ function configuredApps(root, supplied) {
83
85
  };
84
86
  for (const field of Object.keys(allowedFields)){
85
87
  const value = app[field];
86
- if (void 0 !== value && !allowedFields[field].includes(value)) throw new Error(`${key}: invalid ${field}`);
88
+ if ((void 0 !== value || 'kind' === field) && ('string' != typeof value || !allowedFields[field].includes(value))) throw new Error(`${key}: invalid ${field}`);
87
89
  }
88
90
  if (void 0 !== app.api) {
89
91
  if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
@@ -125,6 +127,9 @@ function check(options, topology) {
125
127
  };
126
128
  try {
127
129
  const apps = configuredApps(root, options.configuredApps);
130
+ const workspace = node_fs.realpathSync(root);
131
+ const realRelative = (file)=>normalize(node_path.relative(workspace, node_fs.realpathSync(absolute(file))));
132
+ const inside = (directory, real)=>real.startsWith(`${directory}${node_path.sep}`);
128
133
  const byPath = new Map(apps.map((app)=>[
129
134
  normalize(app.path),
130
135
  app
@@ -165,12 +170,19 @@ function check(options, topology) {
165
170
  const resolveRelative = (fromFile, specifier)=>{
166
171
  if (!/^\.\.?\//u.test(specifier)) return;
167
172
  const base = node_path.posix.join(node_path.posix.dirname(fromFile), specifier).replace(/\.(?:[cm]?[jt]sx?)$/u, '');
168
- return [
173
+ const candidate = [
169
174
  `${base}.ts`,
170
175
  `${base}.tsx`,
171
176
  `${base}/index.ts`,
172
177
  `${base}/index.tsx`
173
- ].find((candidate)=>exists(candidate));
178
+ ].find((file)=>exists(file));
179
+ return void 0 === candidate ? void 0 : realRelative(candidate);
180
+ };
181
+ const graphBinding = (graph, module, expression, seen = new Set())=>{
182
+ if (seen.has(expression) || seen.size >= 128) return;
183
+ seen.add(expression);
184
+ const binding = __rspack_external__babel_types_6e8337dd.isIdentifier(expression) || __rspack_external__babel_types_6e8337dd.isMemberExpression(expression) ? graph.resolveReference(module, expression) : void 0;
185
+ return binding?.kind === 'declaration' && (__rspack_external__babel_types_6e8337dd.isIdentifier(binding.expression) || __rspack_external__babel_types_6e8337dd.isMemberExpression(binding.expression)) ? graphBinding(graph, binding.module, binding.expression, seen) : binding;
174
186
  };
175
187
  const clientExportFile = (appPath, exported)=>{
176
188
  if ('string' != typeof exported || !exported.startsWith('./') || exported.split(/[\\/]/u).includes('..') || !/\.[cm]?[jt]sx?$/u.test(exported)) return;
@@ -187,29 +199,65 @@ function check(options, topology) {
187
199
  return normalize(node_path.relative(root, candidate));
188
200
  };
189
201
  const composedModules = (file, appPath)=>{
190
- const order = [];
191
- const seen = new Set();
202
+ const owner = node_fs.realpathSync(absolute(appPath));
203
+ if (!inside(owner, node_fs.realpathSync(absolute(file)))) throw new Error(`consumer module closure escapes its owner: ${file}`);
204
+ const entry = realRelative(file);
205
+ const discovered = new Set([
206
+ entry
207
+ ]);
192
208
  const queue = [
193
- file
209
+ {
210
+ file: entry,
211
+ depth: 0
212
+ }
194
213
  ];
195
- const owner = node_fs.realpathSync(absolute(appPath));
196
- while(queue.length > 0 && order.length < 128){
197
- const current = queue.shift();
198
- if (seen.has(current)) continue;
199
- seen.add(current);
200
- order.push(current);
214
+ let graph;
215
+ for (const { file: current, depth } of queue){
201
216
  const ast = parseFile(current);
202
217
  if (!ast) continue;
203
- for (const statement of ast.program.body){
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;
205
- const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
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);
218
+ const sources = [];
219
+ let callsRequire = false;
220
+ for (const statement of ast.program.body)if (__rspack_external__babel_types_6e8337dd.isImportDeclaration(statement) && 'type' !== statement.importKind && 'typeof' !== statement.importKind || (__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || __rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement)) && 'type' !== statement.exportKind && statement.source) sources.push(statement.source.value);
221
+ __rspack_external__babel_types_6e8337dd.traverseFast(ast, (node)=>{
222
+ if (__rspack_external__babel_types_6e8337dd.isImportExpression(node)) {
223
+ if (!__rspack_external__babel_types_6e8337dd.isStringLiteral(node.source)) throw new Error(`consumer module closure has nonliteral runtime dependency: ${current}`);
224
+ sources.push(node.source.value);
225
+ }
226
+ if ((__rspack_external__babel_types_6e8337dd.isCallExpression(node) || __rspack_external__babel_types_6e8337dd.isOptionalCallExpression(node)) && __rspack_external__babel_types_6e8337dd.isIdentifier(unwrapExpression(node.callee, true), {
227
+ name: 'require'
228
+ })) callsRequire = true;
229
+ });
230
+ if (callsRequire) {
231
+ graph ??= createModuleGraph();
232
+ const module = graph.module(absolute(current));
233
+ __rspack_external__babel_types_6e8337dd.traverseFast(module.file, (node)=>{
234
+ if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) && !__rspack_external__babel_types_6e8337dd.isOptionalCallExpression(node)) return;
235
+ const callee = unwrapExpression(node.callee, true);
236
+ if (!__rspack_external__babel_types_6e8337dd.isIdentifier(callee, {
237
+ name: 'require'
238
+ }) || !graph.isUnboundReference(module, callee)) return;
239
+ const [argument] = node.arguments;
240
+ if (1 !== node.arguments.length || !__rspack_external__babel_types_6e8337dd.isStringLiteral(argument)) throw new Error(`consumer module closure has nonliteral runtime dependency: ${current}`);
241
+ sources.push(argument.value);
242
+ });
243
+ }
244
+ for (const source of sources){
245
+ if (!/^\.\.?\//u.test(source)) continue;
246
+ const resolved = resolveRelative(current, source);
247
+ if (void 0 === resolved) throw new Error(`consumer module closure has unresolved relative dependency: ${current} -> ${source}`);
248
+ if (!inside(owner, node_path.join(workspace, resolved))) throw new Error(`consumer module closure escapes its owner: ${current} -> ${source}`);
249
+ if (!discovered.has(resolved)) {
250
+ if (depth + 1 >= MAX_CLOSURE_DEPTH) throw new Error(`consumer module closure exceeds ${MAX_CLOSURE_DEPTH} import-depth analysis budget`);
251
+ if (discovered.size >= MAX_CLOSURE_MODULES) throw new Error(`consumer module closure exceeds ${MAX_CLOSURE_MODULES}-module analysis budget`);
252
+ discovered.add(resolved);
253
+ queue.push({
254
+ file: resolved,
255
+ depth: depth + 1
256
+ });
209
257
  }
210
258
  }
211
259
  }
212
- return order;
260
+ return queue.map((item)=>item.file);
213
261
  };
214
262
  const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
215
263
  if (!exists(file)) return;
@@ -248,6 +296,10 @@ function check(options, topology) {
248
296
  })), `${file}: must default-export ${defaultExport}`);
249
297
  };
250
298
  const effect = '@modern-js/bff-effect/effect-client';
299
+ const clientFactories = new Set([
300
+ 'makeEffectHttpApiClient',
301
+ 'makeEffectRpcClient'
302
+ ]);
251
303
  const appDirectories = [
252
304
  ...new Set([
253
305
  ...directories('apps'),
@@ -468,8 +520,8 @@ function check(options, topology) {
468
520
  ]);
469
521
  });
470
522
  const sourceRules = options.sourceRules ?? [];
471
- const graph = createModuleGraph();
472
523
  for (const file of sourceRules.length ? sourceFiles : [])if (/\.[cm]?[jt]sx?$/u.test(file)) guarded(file, ()=>{
524
+ const graph = createModuleGraph();
473
525
  let module;
474
526
  try {
475
527
  module = graph.module(absolute(file));
@@ -488,20 +540,178 @@ function check(options, topology) {
488
540
  for (const message of messages)diagnostics.push(`${file}: ${message}`);
489
541
  }
490
542
  });
491
- const shell = 'apps/shell-super-app';
492
- const verticals = apps.filter((app)=>app.path.startsWith('verticals/') && apiApp(app));
493
- if (exists(shell) && verticals.length) {
494
- const file = `${shell}/src/api/vertical-clients.ts`;
495
- assert(exists(file), `${file}: must aggregate vertical API clients`);
496
- if (exists(file)) guarded(file, ()=>{
497
- const ast = parseFile(file);
498
- for (const app of verticals){
499
- const specifier = `${app.package}/api/${app.api?.protocol === 'rpc' ? 'rpc-client' : 'client'}`;
500
- assert(ast?.program.body.some((item)=>__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(item) && item.source?.value === specifier), `${file}: must re-export ${specifier}`);
543
+ const checkShellExports = (shellPath)=>{
544
+ const packageFile = `${shellPath}/package.json`;
545
+ const manifest = JSON.parse(read(packageFile));
546
+ noPath(`${shellPath}/src/api/vertical-clients.ts`, 'Shell client registries are forbidden; import each owner client directly');
547
+ assert(!Object.hasOwn(manifest.exports ?? {}, './api/clients'), `${packageFile}: Shell ./api/clients exports are forbidden`);
548
+ const owner = node_fs.realpathSync(absolute(shellPath));
549
+ const invalidExport = ()=>assert(false, `${packageFile}: invalid Shell public source export`);
550
+ const targets = (exported)=>'string' == typeof exported ? [
551
+ exported
552
+ ] : exported && 'object' == typeof exported ? Object.values(exported).flatMap(targets) : [];
553
+ const filesUnder = (directory, seen = new Set())=>{
554
+ const real = node_fs.realpathSync(absolute(directory));
555
+ if (real !== owner && !inside(owner, real)) {
556
+ invalidExport();
557
+ return [];
558
+ }
559
+ if (seen.has(real)) return [];
560
+ seen.add(real);
561
+ return node_fs.readdirSync(absolute(directory), {
562
+ withFileTypes: true
563
+ }).flatMap((entry)=>{
564
+ if (ignored.has(entry.name)) return [];
565
+ const file = `${directory}/${entry.name}`;
566
+ const stat = node_fs.statSync(absolute(file));
567
+ return stat.isDirectory() ? filesUnder(file, seen) : stat.isFile() ? [
568
+ file
569
+ ] : [];
570
+ });
571
+ };
572
+ const exportedFiles = (target)=>{
573
+ if (!target.includes('*')) return [
574
+ `${shellPath}/${target.slice(2)}`
575
+ ];
576
+ const prefix = target.slice(0, target.indexOf('*'));
577
+ const pattern = new RegExp(`^${target.split('*').map((part)=>part.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&')).join('.*')}$`, 'u');
578
+ return filesUnder(`${shellPath}/${prefix.slice(2, prefix.lastIndexOf('/'))}`).filter((file)=>pattern.test(`./${node_path.posix.relative(shellPath, file)}`));
579
+ };
580
+ const ownClientPrefix = `${manifest.name}/api/`;
581
+ const foreignSpecifier = (specifier)=>/\/api\/(?:rpc-)?client$/u.test(specifier) && !specifier.startsWith(ownClientPrefix);
582
+ for (const target of targets(manifest.exports))if (/\.[cm]?[jt]sx?$/u.test(target)) {
583
+ if (!target.startsWith('./') || target.split(/[\\/]/u).includes('..')) {
584
+ invalidExport();
585
+ continue;
501
586
  }
502
- });
587
+ for (const candidate of exportedFiles(target)){
588
+ const file = clientExportFile(shellPath, `./${node_path.posix.relative(shellPath, candidate)}`);
589
+ if (!file) {
590
+ invalidExport();
591
+ continue;
592
+ }
593
+ assert(!exposesForeignClient(file, owner, foreignSpecifier), `${file}: Shell public exports must not expose another owner's API client`);
594
+ }
595
+ }
596
+ };
597
+ const exposesForeignClient = (entry, owner, foreignSpecifier)=>{
598
+ const graph = createModuleGraph();
599
+ const seenValues = new Set();
600
+ const seenExports = new Map();
601
+ const workspacePath = (module)=>normalize(node_path.relative(workspace, module.path));
602
+ const resolveSource = (from, specifier)=>resolveRelative(from, specifier) ?? ownerClients.get(specifier);
603
+ const returnExpressions = (node)=>{
604
+ if (__rspack_external__babel_types_6e8337dd.isReturnStatement(node)) return node.argument ? [
605
+ node.argument
606
+ ] : [];
607
+ if (__rspack_external__babel_types_6e8337dd.isFunction(node)) return [];
608
+ return (__rspack_external__babel_types_6e8337dd.VISITOR_KEYS[node.type] ?? []).flatMap((key)=>{
609
+ const value = Reflect.get(node, key);
610
+ return (Array.isArray(value) ? value : [
611
+ value
612
+ ]).flatMap((child)=>__rspack_external__babel_types_6e8337dd.isNode(child) ? returnExpressions(child) : []);
613
+ });
614
+ };
615
+ const nestedValues = (node)=>{
616
+ if (__rspack_external__babel_types_6e8337dd.isObjectExpression(node)) return node.properties.flatMap((property)=>__rspack_external__babel_types_6e8337dd.isObjectProperty(property) ? [
617
+ property.value
618
+ ] : __rspack_external__babel_types_6e8337dd.isObjectMethod(property) ? returnExpressions(property.body) : __rspack_external__babel_types_6e8337dd.isSpreadElement(property) ? [
619
+ property.argument
620
+ ] : []);
621
+ if (__rspack_external__babel_types_6e8337dd.isArrayExpression(node)) return node.elements.filter((element)=>null !== element);
622
+ if (__rspack_external__babel_types_6e8337dd.isFunction(node)) return __rspack_external__babel_types_6e8337dd.isBlockStatement(node.body) ? returnExpressions(node.body) : [
623
+ node.body
624
+ ];
625
+ return [];
626
+ };
627
+ const foreignValue = (value, from)=>{
628
+ if ('external' === value.kind) {
629
+ const source = resolveSource(workspacePath(from), value.specifier);
630
+ if (!source) return foreignSpecifier(value.specifier);
631
+ return '*' === value.name ? inspect(source) : exportedValue(source, value.name);
632
+ }
633
+ if ('unresolved' === value.kind && __rspack_external__babel_types_6e8337dd.isImportDeclaration(value.node) && value.node.specifiers.some((specifier)=>__rspack_external__babel_types_6e8337dd.isImportNamespaceSpecifier(specifier))) {
634
+ const specifier = value.node.source.value;
635
+ const source = resolveSource(workspacePath(value.module), specifier);
636
+ return source ? inspect(source) : foreignSpecifier(specifier);
637
+ }
638
+ if ('node' !== value.kind || seenValues.has(value.node)) return false;
639
+ seenValues.add(value.node);
640
+ const { node, module } = value;
641
+ if ((__rspack_external__babel_types_6e8337dd.isCallExpression(node) || __rspack_external__babel_types_6e8337dd.isOptionalCallExpression(node)) && graph.evaluate(module, node.callee).some((callee)=>'external' === callee.kind && callee.specifier === effect && clientFactories.has(callee.name))) {
642
+ const [argument] = node.arguments;
643
+ const binding = argument ? graphBinding(graph, module, argument) : void 0;
644
+ if (module.boundary !== owner || binding?.kind !== 'declaration' || binding.module.boundary !== owner) return true;
645
+ }
646
+ return nestedValues(node).some((expression)=>graph.evaluate(module, expression).some((nested)=>foreignValue(nested, module)));
647
+ };
648
+ const exportName = (node)=>__rspack_external__babel_types_6e8337dd.isIdentifier(node) ? node.name : node.value;
649
+ const names = (source, visited = new Set())=>{
650
+ if (visited.has(source)) return [];
651
+ visited.add(source);
652
+ return graph.module(absolute(source)).file.program.body.flatMap((statement)=>{
653
+ if (__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement)) return [
654
+ 'default'
655
+ ];
656
+ if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) && !__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) return [];
657
+ if (__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement)) {
658
+ const target = resolveSource(source, statement.source.value);
659
+ return target ? names(target, visited).filter((name)=>'default' !== name) : [];
660
+ }
661
+ return statement.declaration ? Object.keys(__rspack_external__babel_types_6e8337dd.getOuterBindingIdentifiers(statement.declaration)) : statement.specifiers.flatMap((specifier)=>__rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier) && 'type' === specifier.exportKind ? [] : [
662
+ exportName(specifier.exported)
663
+ ]);
664
+ });
665
+ };
666
+ const exportedValue = (source, name)=>{
667
+ const visited = seenExports.get(source) ?? new Set();
668
+ if (visited.has(name)) return false;
669
+ visited.add(name);
670
+ seenExports.set(source, visited);
671
+ const module = graph.module(absolute(source));
672
+ const evaluates = (from, node)=>graph.evaluate(from, node).some((value)=>foreignValue(value, from));
673
+ const binding = graph.resolve(module, name, 'export');
674
+ if (binding?.kind === 'external') return foreignValue({
675
+ ...binding,
676
+ members: []
677
+ }, module);
678
+ if (binding?.kind === 'declaration') return evaluates(binding.module, binding.expression);
679
+ for (const statement of module.file.program.body){
680
+ if (__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement) && 'default' === name) return evaluates(module, statement.declaration);
681
+ if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
682
+ if (__rspack_external__babel_types_6e8337dd.isFunctionDeclaration(statement.declaration) && statement.declaration.id?.name === name) return evaluates(module, statement.declaration);
683
+ const specifier = statement.specifiers.find((item)=>exportName(item.exported) === name);
684
+ if (!specifier) continue;
685
+ if (__rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier) && 'type' === specifier.exportKind) return false;
686
+ if (!statement.source) return __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier) ? evaluates(module, specifier.local) : false;
687
+ const target = resolveSource(source, statement.source.value);
688
+ if (!target) return foreignSpecifier(statement.source.value);
689
+ if (__rspack_external__babel_types_6e8337dd.isExportNamespaceSpecifier(specifier)) return inspect(target);
690
+ if (__rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return exportedValue(target, exportName(specifier.local));
691
+ }
692
+ return module.file.program.body.some((statement)=>{
693
+ if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) return false;
694
+ const target = resolveSource(source, statement.source.value);
695
+ return !!target && names(target).includes(name) && exportedValue(target, name);
696
+ });
697
+ };
698
+ const inspect = (source)=>[
699
+ ...new Set(names(source))
700
+ ].some((name)=>exportedValue(source, name));
701
+ return inspect(entry);
702
+ };
703
+ const ownerClients = new Map();
704
+ for (const app of apps)if (exists(`${app.path}/package.json`)) {
705
+ const manifest = JSON.parse(read(`${app.path}/package.json`));
706
+ for (const key of [
707
+ './api/client',
708
+ './api/rpc-client'
709
+ ]){
710
+ const file = clientExportFile(app.path, manifest.exports?.[key]);
711
+ if (file) ownerClients.set(`${manifest.name}${key.slice(1)}`, file);
712
+ }
503
713
  }
504
- 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`));
714
+ for (const shell of apps.filter((app)=>'shell' === app.kind))guarded(shell.path, ()=>checkShellExports(normalize(shell.path)));
505
715
  if (exists('topology/reference-topology.json')) guarded('topology/reference-topology.json', ()=>{
506
716
  for (const vertical of JSON.parse(read('topology/reference-topology.json')).verticals ?? []){
507
717
  if (vertical.api?.runtime === 'effect') {
@@ -187,6 +187,7 @@ function parseModule(filePath, workspace) {
187
187
  const file = parseSource(node_fs.readFileSync(filePath, 'utf8'), filePath);
188
188
  let scope;
189
189
  const references = new Map();
190
+ const declarations = new Map();
190
191
  const returns = new Map();
191
192
  const bareReturns = new Set();
192
193
  const functionScopes = new Map();
@@ -297,6 +298,11 @@ function parseModule(filePath, workspace) {
297
298
  ReferencedIdentifier (p) {
298
299
  references.set(p.node, p.scope.getBinding(p.node.name));
299
300
  },
301
+ BindingIdentifier (p) {
302
+ if (!__rspack_external__babel_types_6e8337dd.isIdentifier(p.node)) return;
303
+ const binding = p.scope.getBinding(p.node.name);
304
+ if (binding?.identifier === p.node) declarations.set(p.node, binding);
305
+ },
300
306
  Function (p) {
301
307
  functionScopes.set(p.node, p.scope);
302
308
  },
@@ -362,6 +368,7 @@ function parseModule(filePath, workspace) {
362
368
  scope,
363
369
  workspace,
364
370
  references,
371
+ declarations,
365
372
  returns,
366
373
  bareReturns,
367
374
  functionScopes,
@@ -1097,7 +1104,7 @@ function createModuleGraph() {
1097
1104
  }
1098
1105
  };
1099
1106
  const mutated = (module, binding)=>binding.constantViolations.length > 0 || factsOf(module).merged.has(binding.identifier.name) || (aliasesOf(binding, factsOf(module).writes)?.some((alias)=>factsOf(module).writes.has(alias.binding)) ?? true);
1100
- const resolveBinding = (module, name, scope, chain, seen)=>{
1107
+ const resolveBinding = (module, name, scope, chain, seen, lexical)=>{
1101
1108
  const key = `${scope}:${module.path}#${name}`;
1102
1109
  if (seen.has(key) || seen.size >= MAX_LOOKUPS) return;
1103
1110
  seen.add(key);
@@ -1120,7 +1127,7 @@ function createModuleGraph() {
1120
1127
  return void 0 === next ? void 0 : resolveBinding(next, imported, 'export', hops, seen);
1121
1128
  };
1122
1129
  if ('local' === scope) {
1123
- const binding = factsOf(module).scope.getBinding(name);
1130
+ const binding = lexical ?? factsOf(module).scope.getBinding(name);
1124
1131
  if (!binding || mutated(module, binding)) return;
1125
1132
  if ('module' === binding.kind) {
1126
1133
  const imported = importOf(binding);
@@ -1149,6 +1156,39 @@ function createModuleGraph() {
1149
1156
  if (void 0 !== resolved) return resolved;
1150
1157
  }
1151
1158
  };
1159
+ const resolveReference = (module, reference)=>{
1160
+ const node = unwrapExpression(reference, true);
1161
+ const moduleFacts = factsOf(module);
1162
+ const bindingAt = (identifier)=>moduleFacts.references.get(identifier) ?? moduleFacts.declarations.get(identifier);
1163
+ if (__rspack_external__babel_types_6e8337dd.isIdentifier(node)) {
1164
+ const binding = bindingAt(node);
1165
+ return binding ? resolveBinding(module, node.name, 'local', [], new Set(), binding) : void 0;
1166
+ }
1167
+ if (!__rspack_external__babel_types_6e8337dd.isMemberExpression(node) || node.computed || !__rspack_external__babel_types_6e8337dd.isIdentifier(node.object) || !__rspack_external__babel_types_6e8337dd.isIdentifier(node.property)) return;
1168
+ const binding = bindingAt(node.object);
1169
+ if (!binding || 'module' !== binding.kind || mutated(module, binding)) return;
1170
+ const imported = importOf(binding);
1171
+ if (imported?.name !== '*') return;
1172
+ const target = follow(module, imported.specifier);
1173
+ const chain = [
1174
+ {
1175
+ path: module.path,
1176
+ name: node.object.name
1177
+ }
1178
+ ];
1179
+ if ('external' === target) return {
1180
+ kind: 'external',
1181
+ specifier: imported.specifier,
1182
+ name: node.property.name,
1183
+ chain
1184
+ };
1185
+ return void 0 === target ? void 0 : resolveBinding(target, node.property.name, 'export', chain, new Set());
1186
+ };
1187
+ const isUnboundReference = (module, reference)=>{
1188
+ const node = unwrapExpression(reference, true);
1189
+ const { references } = factsOf(module);
1190
+ return __rspack_external__babel_types_6e8337dd.isIdentifier(node) && references.has(node) && void 0 === references.get(node);
1191
+ };
1152
1192
  const evaluation = (walking)=>{
1153
1193
  const active = new Set();
1154
1194
  let frames = 0;
@@ -1756,6 +1796,8 @@ function createModuleGraph() {
1756
1796
  return {
1757
1797
  module: (filePath)=>load(filePath),
1758
1798
  resolve: (module, name, scope)=>resolveBinding(module, name, scope, [], new Set()),
1799
+ resolveReference,
1800
+ isUnboundReference,
1759
1801
  evaluate: (module, expression)=>evaluation(false).values(module, expression, []),
1760
1802
  reachable: (module, expression)=>evaluation(true).reachable(module, expression)
1761
1803
  };
@@ -8,6 +8,19 @@ const nodeRuntime = '@modern-js/bff-effect/effect';
8
8
  const assembly = '@modern-js/bff-effect/assembly';
9
9
  const failure = 'Generated API entries must export defineEffectBff(...) or the native Effect BFF assembly helper with an explicitly composed handler Layer and an unshadowed executable root; entries must implement handlers through HttpApiBuilder.group.';
10
10
  const MAX_API_SOURCE_MODULES = 256;
11
+ const httpRouteMethods = new Set([
12
+ '*',
13
+ 'GET',
14
+ 'POST',
15
+ 'PUT',
16
+ 'PATCH',
17
+ 'DELETE',
18
+ 'HEAD',
19
+ 'OPTIONS',
20
+ 'TRACE',
21
+ 'CONNECT',
22
+ 'QUERY'
23
+ ]);
11
24
  function createEffectApiImportResolver(filename) {
12
25
  const directory = node_fs.realpathSync(node_path.dirname(filename));
13
26
  const owner = node_fs.realpathSync(node_path.resolve(directory, '..'));
@@ -55,9 +68,12 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
55
68
  })) {
56
69
  let target = nodePath.isAssignmentExpression() ? nodePath.node.left : nodePath.node.argument;
57
70
  while(__rspack_external__babel_types_6e8337dd.isMemberExpression(target))target = target.object;
58
- if (__rspack_external__babel_types_6e8337dd.isIdentifier(target)) {
59
- const binding = nodePath.scope.getBinding(target.name);
60
- if (binding) mutated.add(binding);
71
+ let binding = __rspack_external__babel_types_6e8337dd.isIdentifier(target) ? nodePath.scope.getBinding(target.name) : void 0;
72
+ while(binding && !mutated.has(binding)){
73
+ mutated.add(binding);
74
+ const declarator = binding.path.node;
75
+ const alias = 'const' === binding.kind && __rspack_external__babel_types_6e8337dd.isVariableDeclarator(declarator) && declarator.init ? unwrapExpression(declarator.init, true) : void 0;
76
+ binding = __rspack_external__babel_types_6e8337dd.isIdentifier(alias) ? binding.path.scope.getBinding(alias.name) : void 0;
61
77
  }
62
78
  }
63
79
  if (nodePath.isTSModuleDeclaration() && __rspack_external__babel_types_6e8337dd.isIdentifier(nodePath.node.id)) {
@@ -232,11 +248,48 @@ function strictEffectRuntimeTopologyViolation(source, resolveImport) {
232
248
  if (method(node.callee, 'Layer', 'mergeAll', layerSources)) return node.arguments.length > 0 && node.arguments.every((argument)=>handlers(argument, api));
233
249
  return method(node.callee, 'HttpApiBuilder', 'group') && 3 === node.arguments.length && apiIdentity(node.arguments[0]) === api && handled(node.arguments[2]);
234
250
  });
235
- const transport = (node)=>guarded(node, (node)=>{
251
+ const effectSources = [
252
+ edge,
253
+ 'effect',
254
+ 'effect/Effect'
255
+ ];
256
+ const routeHandler = (node)=>{
257
+ node = unwrap(node);
258
+ if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) || !method(node.callee, 'HttpRouter', 'add') || 3 !== node.arguments.length) return;
259
+ const [verb, route, handler] = node.arguments;
260
+ return __rspack_external__babel_types_6e8337dd.isStringLiteral(verb) && httpRouteMethods.has(verb.value) && __rspack_external__babel_types_6e8337dd.isStringLiteral(route) && (route.value.startsWith('/') || '*' === route.value) && (__rspack_external__babel_types_6e8337dd.isArrowFunctionExpression(handler) || __rspack_external__babel_types_6e8337dd.isFunctionExpression(handler)) ? handler : void 0;
261
+ };
262
+ const effectReference = (node)=>guarded(node, (node)=>{
236
263
  const init = initialized(node);
237
- if (init) return transport(init);
264
+ if (init) return __rspack_external__babel_types_6e8337dd.isIdentifier(unwrap(init)) && effectReference(init);
265
+ const value = imported(node);
266
+ return !!value && !value.namespace;
267
+ });
268
+ const capturedRouteFactory = (node)=>guarded(node, (node)=>{
269
+ const init = initialized(node) ?? externalValue(node);
270
+ if (init) return capturedRouteFactory(init);
271
+ if (!__rspack_external__babel_types_6e8337dd.isArrowFunctionExpression(node) || node.async || 1 !== node.params.length || !__rspack_external__babel_types_6e8337dd.isIdentifier(node.params[0]) || __rspack_external__babel_types_6e8337dd.isBlockStatement(node.body)) return false;
272
+ const handler = routeHandler(node.body);
273
+ const parameter = binding(node.params[0]);
274
+ return !!handler && !!parameter && parameter.referencePaths.some((reference)=>!!reference.findParent((path)=>path.node === handler));
275
+ });
276
+ const capturedTransport = (node)=>guarded(node, (node)=>{
277
+ const init = initialized(node);
278
+ if (init) return capturedTransport(init);
279
+ if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node)) return false;
280
+ if (method(node.callee, 'Effect', 'map', effectSources)) return 2 === node.arguments.length && effectReference(node.arguments[0]) && capturedRouteFactory(node.arguments[1]);
281
+ if (!__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) || node.callee.computed || !__rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property, {
282
+ name: 'pipe'
283
+ }) || 1 !== node.arguments.length || !effectReference(node.callee.object)) return false;
284
+ const map = unwrap(node.arguments[0]);
285
+ return __rspack_external__babel_types_6e8337dd.isCallExpression(map) && method(map.callee, 'Effect', 'map', effectSources) && 1 === map.arguments.length && capturedRouteFactory(map.arguments[0]);
286
+ });
287
+ const transport = (node, provided = false)=>guarded(node, (node)=>{
288
+ const init = initialized(node) ?? externalValue(node);
289
+ if (init) return transport(init, provided);
238
290
  if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node)) return method(node, 'Layer', 'empty', layerSources);
239
- return pipe(node, transport) || method(node.callee, 'HttpRouter', 'cors') || method(node.callee, 'Layer', 'mergeAll', layerSources) && node.arguments.length > 0 && node.arguments.every(transport);
291
+ const providedHere = provided || node.arguments.some((argument)=>__rspack_external__babel_types_6e8337dd.isCallExpression(argument) && method(argument.callee, 'Layer', 'provide', layerSources) && 1 === argument.arguments.length);
292
+ return pipe(node, (value)=>transport(value, providedHere)) || void 0 !== routeHandler(node) || provided && method(node.callee, 'Layer', 'unwrap', layerSources) && 1 === node.arguments.length && capturedTransport(node.arguments[0]) || method(node.callee, 'HttpRouter', 'cors') || method(node.callee, 'Layer', 'mergeAll', layerSources) && node.arguments.length > 0 && node.arguments.every((argument)=>transport(argument, provided));
240
293
  });
241
294
  const directLayer = (node, api)=>guarded(node, (node)=>{
242
295
  const init = initialized(node);
@@ -6,7 +6,8 @@ export { createModuleGraph } from './module-graph.js';
6
6
  export interface MicroVerticalConfiguredApp {
7
7
  readonly path: string;
8
8
  readonly id?: string;
9
- readonly kind?: string;
9
+ /** `shell` or `vertical`; selects the rules the app is checked against. */
10
+ readonly kind: string;
10
11
  readonly package?: string;
11
12
  readonly surfaceProfile?: string;
12
13
  readonly deliveryUnitKind?: string;
@@ -22,7 +23,7 @@ export interface MicroVerticalApiSourceRuleContext {
22
23
  /** Workspace-relative path of `module`. */
23
24
  readonly file: string;
24
25
  readonly module: SourceModule;
25
- /** One graph per check, shared by every rule and source file. */
26
+ /** One graph per source file, shared by every rule for that file. */
26
27
  readonly graph: ModuleGraph;
27
28
  }
28
29
  /** A consumer-owned source rule; each returned message is a violation. */
@@ -67,6 +67,14 @@ export interface ModuleGraph {
67
67
  * over-budget bindings return undefined so callers fail closed.
68
68
  */
69
69
  resolve(module: SourceModule, name: string, scope: 'export' | 'local'): ResolvedBinding | undefined;
70
+ /**
71
+ * `resolve` for one lexical occurrence: an identifier `module` references
72
+ * or declares, or `namespace.member` on a namespace import. The binding is
73
+ * the one in scope at that node, not the module-scope name.
74
+ */
75
+ resolveReference(module: SourceModule, reference: t.Node): ResolvedBinding | undefined;
76
+ /** True only for an identifier `module` references that has no binding (a global). */
77
+ isUnboundReference(module: SourceModule, reference: t.Node): boolean;
70
78
  /**
71
79
  * Every value `expression` (a node of `module`) can evaluate to, following
72
80
  * any binding kind, destructuring, member access into object literals,
package/package.json CHANGED
@@ -23,7 +23,7 @@
23
23
  "engines": {
24
24
  "node": ">=20"
25
25
  },
26
- "version": "3.9.3-ultramodern.1",
26
+ "version": "3.9.3-ultramodern.2",
27
27
  "types": "./dist/types/index.d.ts",
28
28
  "main": "./dist/esm-node/index.js",
29
29
  "typesVersions": {