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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -28,8 +28,10 @@ var __webpack_require__ = {};
28
28
  var __webpack_exports__ = {};
29
29
  __webpack_require__.r(__webpack_exports__);
30
30
  __webpack_require__.d(__webpack_exports__, {
31
+ camelCaseStem: ()=>camelCaseStem,
31
32
  configuredMicroVerticalApiStem: ()=>configuredMicroVerticalApiStem,
32
- microVerticalApiBaselineViolation: ()=>microVerticalApiBaselineViolation
33
+ microVerticalApiBaselineViolation: ()=>microVerticalApiBaselineViolation,
34
+ pascalCaseStem: ()=>pascalCaseStem
33
35
  });
34
36
  const types_namespaceObject = require("@babel/types");
35
37
  const external_microvertical_api_owner_cjs_namespaceObject = require("./microvertical-api-owner.cjs");
@@ -503,11 +505,15 @@ const microVerticalApiBaselineViolation = (stem, filePath, expectation)=>{
503
505
  throw error;
504
506
  }
505
507
  };
508
+ exports.camelCaseStem = __webpack_exports__.camelCaseStem;
506
509
  exports.configuredMicroVerticalApiStem = __webpack_exports__.configuredMicroVerticalApiStem;
507
510
  exports.microVerticalApiBaselineViolation = __webpack_exports__.microVerticalApiBaselineViolation;
511
+ exports.pascalCaseStem = __webpack_exports__.pascalCaseStem;
508
512
  for(var __rspack_i in __webpack_exports__)if (-1 === [
513
+ "camelCaseStem",
509
514
  "configuredMicroVerticalApiStem",
510
- "microVerticalApiBaselineViolation"
515
+ "microVerticalApiBaselineViolation",
516
+ "pascalCaseStem"
511
517
  ].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
512
518
  Object.defineProperty(exports, '__esModule', {
513
519
  value: true
@@ -64,6 +64,14 @@ const ignored = new Set([
64
64
  'node_modules',
65
65
  'repos'
66
66
  ]);
67
+ const MAX_CLOSURE_DEPTH = 128;
68
+ const MAX_CLOSURE_MODULES = 2048;
69
+ const MAX_API_HOPS = 128;
70
+ const MAX_API_VISITS = 2048;
71
+ const identitySources = [
72
+ 'effect',
73
+ 'effect/Function'
74
+ ];
67
75
  const normalize = (file)=>file.split(external_node_path_default().sep).join('/').replace(/^\.\//u, '');
68
76
  const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
69
77
  const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
@@ -129,7 +137,7 @@ function configuredApps(root, supplied) {
129
137
  };
130
138
  for (const field of Object.keys(allowedFields)){
131
139
  const value = app[field];
132
- if (void 0 !== value && !allowedFields[field].includes(value)) throw new Error(`${key}: invalid ${field}`);
140
+ if ((void 0 !== value || 'kind' === field) && ('string' != typeof value || !allowedFields[field].includes(value))) throw new Error(`${key}: invalid ${field}`);
133
141
  }
134
142
  if (void 0 !== app.api) {
135
143
  if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
@@ -171,6 +179,9 @@ function check(options, topology) {
171
179
  };
172
180
  try {
173
181
  const apps = configuredApps(root, options.configuredApps);
182
+ const workspace = external_node_fs_default().realpathSync(root);
183
+ const realRelative = (file)=>normalize(external_node_path_default().relative(workspace, external_node_fs_default().realpathSync(absolute(file))));
184
+ const inside = (directory, real)=>real.startsWith(`${directory}${external_node_path_default().sep}`);
174
185
  const byPath = new Map(apps.map((app)=>[
175
186
  normalize(app.path),
176
187
  app
@@ -211,12 +222,19 @@ function check(options, topology) {
211
222
  const resolveRelative = (fromFile, specifier)=>{
212
223
  if (!/^\.\.?\//u.test(specifier)) return;
213
224
  const base = external_node_path_default().posix.join(external_node_path_default().posix.dirname(fromFile), specifier).replace(/\.(?:[cm]?[jt]sx?)$/u, '');
214
- return [
225
+ const candidate = [
215
226
  `${base}.ts`,
216
227
  `${base}.tsx`,
217
228
  `${base}/index.ts`,
218
229
  `${base}/index.tsx`
219
- ].find((candidate)=>exists(candidate));
230
+ ].find((file)=>exists(file));
231
+ return void 0 === candidate ? void 0 : realRelative(candidate);
232
+ };
233
+ const graphBinding = (graph, module, expression, seen = new Set())=>{
234
+ if (seen.has(expression) || seen.size >= 128) return;
235
+ seen.add(expression);
236
+ const binding = types_namespaceObject.isIdentifier(expression) || types_namespaceObject.isMemberExpression(expression) ? graph.resolveReference(module, expression) : void 0;
237
+ return binding?.kind === 'declaration' && (types_namespaceObject.isIdentifier(binding.expression) || types_namespaceObject.isMemberExpression(binding.expression)) ? graphBinding(graph, binding.module, binding.expression, seen) : binding;
220
238
  };
221
239
  const clientExportFile = (appPath, exported)=>{
222
240
  if ('string' != typeof exported || !exported.startsWith('./') || exported.split(/[\\/]/u).includes('..') || !/\.[cm]?[jt]sx?$/u.test(exported)) return;
@@ -233,29 +251,209 @@ function check(options, topology) {
233
251
  return normalize(external_node_path_default().relative(root, candidate));
234
252
  };
235
253
  const composedModules = (file, appPath)=>{
236
- const order = [];
237
- const seen = new Set();
254
+ const owner = external_node_fs_default().realpathSync(absolute(appPath));
255
+ if (!inside(owner, external_node_fs_default().realpathSync(absolute(file)))) throw new Error(`consumer module closure escapes its owner: ${file}`);
256
+ const entry = realRelative(file);
257
+ const discovered = new Set([
258
+ entry
259
+ ]);
238
260
  const queue = [
239
- file
261
+ {
262
+ file: entry,
263
+ depth: 0
264
+ }
240
265
  ];
241
- const owner = external_node_fs_default().realpathSync(absolute(appPath));
242
- while(queue.length > 0 && order.length < 128){
243
- const current = queue.shift();
244
- if (seen.has(current)) continue;
245
- seen.add(current);
246
- order.push(current);
266
+ let graph;
267
+ for (const { file: current, depth } of queue){
247
268
  const ast = parseFile(current);
248
269
  if (!ast) continue;
249
- for (const statement of ast.program.body){
250
- const source = (types_namespaceObject.isImportDeclaration(statement) || types_namespaceObject.isExportNamedDeclaration(statement) || types_namespaceObject.isExportAllDeclaration(statement)) && statement.source ? statement.source.value : void 0;
251
- const resolved = void 0 === source ? void 0 : resolveRelative(current, source);
252
- if (void 0 !== resolved && !seen.has(resolved)) {
253
- const real = external_node_fs_default().realpathSync(absolute(resolved));
254
- if (real.startsWith(`${owner}${external_node_path_default().sep}`)) queue.push(resolved);
270
+ const sources = [];
271
+ let callsRequire = false;
272
+ for (const statement of ast.program.body)if (types_namespaceObject.isImportDeclaration(statement) && 'type' !== statement.importKind && 'typeof' !== statement.importKind || (types_namespaceObject.isExportNamedDeclaration(statement) || types_namespaceObject.isExportAllDeclaration(statement)) && 'type' !== statement.exportKind && statement.source) sources.push(statement.source.value);
273
+ types_namespaceObject.traverseFast(ast, (node)=>{
274
+ if (types_namespaceObject.isImportExpression(node)) {
275
+ if (!types_namespaceObject.isStringLiteral(node.source)) throw new Error(`consumer module closure has nonliteral runtime dependency: ${current}`);
276
+ sources.push(node.source.value);
277
+ }
278
+ if ((types_namespaceObject.isCallExpression(node) || types_namespaceObject.isOptionalCallExpression(node)) && types_namespaceObject.isIdentifier((0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(node.callee, true), {
279
+ name: 'require'
280
+ })) callsRequire = true;
281
+ });
282
+ if (callsRequire) {
283
+ graph ??= (0, external_module_graph_cjs_namespaceObject.createModuleGraph)();
284
+ const module = graph.module(absolute(current));
285
+ types_namespaceObject.traverseFast(module.file, (node)=>{
286
+ if (!types_namespaceObject.isCallExpression(node) && !types_namespaceObject.isOptionalCallExpression(node)) return;
287
+ const callee = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(node.callee, true);
288
+ if (!types_namespaceObject.isIdentifier(callee, {
289
+ name: 'require'
290
+ }) || !graph.isUnboundReference(module, callee)) return;
291
+ const [argument] = node.arguments;
292
+ if (1 !== node.arguments.length || !types_namespaceObject.isStringLiteral(argument)) throw new Error(`consumer module closure has nonliteral runtime dependency: ${current}`);
293
+ sources.push(argument.value);
294
+ });
295
+ }
296
+ for (const source of sources){
297
+ if (!/^\.\.?\//u.test(source)) continue;
298
+ const resolved = resolveRelative(current, source);
299
+ if (void 0 === resolved) throw new Error(`consumer module closure has unresolved relative dependency: ${current} -> ${source}`);
300
+ if (!inside(owner, external_node_path_default().join(workspace, resolved))) throw new Error(`consumer module closure escapes its owner: ${current} -> ${source}`);
301
+ if (!discovered.has(resolved)) {
302
+ if (depth + 1 >= MAX_CLOSURE_DEPTH) throw new Error(`consumer module closure exceeds ${MAX_CLOSURE_DEPTH} import-depth analysis budget`);
303
+ if (discovered.size >= MAX_CLOSURE_MODULES) throw new Error(`consumer module closure exceeds ${MAX_CLOSURE_MODULES}-module analysis budget`);
304
+ discovered.add(resolved);
305
+ queue.push({
306
+ file: resolved,
307
+ depth: depth + 1
308
+ });
309
+ }
310
+ }
311
+ }
312
+ return queue.map((item)=>item.file);
313
+ };
314
+ const foreignClientConstructions = (client, appPath, contract, stem, rpc)=>{
315
+ const graph = (0, external_module_graph_cjs_namespaceObject.createModuleGraph)();
316
+ const contractModule = graph.module(absolute(contract));
317
+ const factory = rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient';
318
+ const ownExport = graph.resolve(contractModule, rpc ? `${(0, external_microvertical_api_baseline_cjs_namespaceObject.pascalCaseStem)(stem)}RpcGroup` : `${(0, external_microvertical_api_baseline_cjs_namespaceObject.camelCaseStem)(stem)}Api`, 'export');
319
+ const isIdentity = (value)=>'external' === value.kind && identitySources.includes(value.specifier) && 'identity' === value.name && 0 === value.members.length;
320
+ const identityPipe = (module, node)=>{
321
+ if (!types_namespaceObject.isCallExpression(node) || !types_namespaceObject.isMemberExpression(node.callee) || node.callee.computed || !types_namespaceObject.isIdentifier(node.callee.property, {
322
+ name: 'pipe'
323
+ }) || 1 !== node.arguments.length) return false;
324
+ const values = graph.evaluate(module, node.arguments[0]);
325
+ return values.length > 0 && values.every(isIdentity);
326
+ };
327
+ const withoutIdentity = (module, value)=>{
328
+ let node = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(value, true);
329
+ for(let pipes = 0; identityPipe(module, node); pipes += 1){
330
+ if (pipes >= MAX_API_HOPS) return value;
331
+ node = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(node.callee.object, true);
332
+ }
333
+ return node;
334
+ };
335
+ const normalizedApi = (initial)=>{
336
+ let binding = initial;
337
+ const seen = new Set();
338
+ for(let hops = 0; hops < MAX_API_HOPS; hops += 1){
339
+ const expression = withoutIdentity(binding.module, binding.expression);
340
+ if (seen.has(expression)) break;
341
+ seen.add(expression);
342
+ if (!types_namespaceObject.isIdentifier(expression) && !types_namespaceObject.isMemberExpression(expression)) return {
343
+ ...binding,
344
+ expression: expression
345
+ };
346
+ const next = graphBinding(graph, binding.module, expression);
347
+ if (next?.kind !== 'declaration') break;
348
+ binding = next;
349
+ }
350
+ };
351
+ const apiBinding = (module, value)=>{
352
+ const binding = graphBinding(graph, module, withoutIdentity(module, value));
353
+ return binding?.kind === 'declaration' ? normalizedApi(binding) : void 0;
354
+ };
355
+ const ownedApis = new Map();
356
+ const own = (binding)=>{
357
+ const identities = ownedApis.get(binding.module.path) ?? new Set();
358
+ identities.add(binding.expression);
359
+ ownedApis.set(binding.module.path, identities);
360
+ };
361
+ const proven = new Set();
362
+ const active = new Set();
363
+ let visits = 0;
364
+ const builtApi = (module, expression)=>{
365
+ const initial = withoutIdentity(module, expression);
366
+ if (active.has(initial) || active.size >= MAX_API_HOPS) return false;
367
+ active.add(initial);
368
+ try {
369
+ const children = [];
370
+ for(let node = initial;;){
371
+ if (++visits > MAX_API_VISITS) return false;
372
+ if (types_namespaceObject.isIdentifier(node) || types_namespaceObject.isMemberExpression(node)) {
373
+ const receiver = apiBinding(module, node);
374
+ if (!receiver || !builtApi(receiver.module, receiver.expression)) return false;
375
+ break;
376
+ }
377
+ if (!types_namespaceObject.isCallExpression(node)) return false;
378
+ const callee = graph.evaluate(module, node.callee);
379
+ if (callee.length > 0 && callee.every((value)=>'external' === value.kind && httpApiSources.includes(value.specifier) && 'HttpApi' === value.name && 1 === value.members.length && 'make' === value.members[0])) {
380
+ if (1 !== node.arguments.length || !types_namespaceObject.isStringLiteral(node.arguments[0])) return false;
381
+ break;
382
+ }
383
+ if (!types_namespaceObject.isMemberExpression(node.callee) || node.callee.computed || !types_namespaceObject.isIdentifier(node.callee.property)) return false;
384
+ const method = node.callee.property.name;
385
+ const count = node.arguments.length;
386
+ const [first] = node.arguments;
387
+ if ('addHttpApi' === method && 1 === count) children.push(first);
388
+ else if (!('add' === method && count > 0 || 'annotate' === method && 2 === count || [
389
+ 'annotateMerge',
390
+ 'middleware'
391
+ ].includes(method) && 1 === count || 'prefix' === method && 1 === count && types_namespaceObject.isStringLiteral(first) && first.value.startsWith('/'))) return false;
392
+ node = withoutIdentity(module, node.callee.object);
255
393
  }
394
+ return children.every((child)=>{
395
+ const binding = apiBinding(module, child);
396
+ return void 0 !== binding && collectApi(binding);
397
+ });
398
+ } finally{
399
+ active.delete(initial);
256
400
  }
401
+ };
402
+ const collectApi = (binding)=>{
403
+ if (proven.has(binding.expression)) return true;
404
+ if (!builtApi(binding.module, binding.expression)) return false;
405
+ proven.add(binding.expression);
406
+ if (binding.module.boundary === contractModule.boundary) own(binding);
407
+ return true;
408
+ };
409
+ if (!rpc && ownExport?.kind === 'declaration') {
410
+ const root = normalizedApi(ownExport);
411
+ if (root && collectApi(root)) own(root);
412
+ else ownedApis.clear();
413
+ }
414
+ const immutablePipeAliases = (module, target)=>{
415
+ if (0 === module.mutations.length) return true;
416
+ let valid = true;
417
+ types_namespaceObject.traverseFast(module.file, (node)=>{
418
+ if (!types_namespaceObject.isVariableDeclarator(node) || !types_namespaceObject.isIdentifier(node.id) || !node.init) return;
419
+ let initializer = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(node.init, true);
420
+ let identityOnly = true;
421
+ let piped = false;
422
+ while(types_namespaceObject.isCallExpression(initializer) && types_namespaceObject.isMemberExpression(initializer.callee) && !initializer.callee.computed && types_namespaceObject.isIdentifier(initializer.callee.property, {
423
+ name: 'pipe'
424
+ })){
425
+ piped = true;
426
+ identityOnly &&= identityPipe(module, initializer);
427
+ initializer = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(initializer.callee.object, true);
428
+ }
429
+ if (!piped) return;
430
+ const receiver = apiBinding(module, initializer);
431
+ if (receiver?.module.path !== target.module.path || receiver.expression !== target.expression) return;
432
+ if (!identityOnly || graph.resolveReference(module, node.id)?.kind !== 'declaration') valid = false;
433
+ });
434
+ return valid;
435
+ };
436
+ const ownedBy = (module, argument)=>{
437
+ if (!argument) return false;
438
+ if (rpc) {
439
+ const binding = graphBinding(graph, module, argument);
440
+ return binding?.kind === 'declaration' && ownExport?.kind === 'declaration' && binding.module.path === ownExport.module.path && binding.expression === ownExport.expression;
441
+ }
442
+ const binding = apiBinding(module, argument);
443
+ return void 0 !== binding && !!ownedApis.get(binding.module.path)?.has(binding.expression) && immutablePipeAliases(module, binding);
444
+ };
445
+ const violations = new Set();
446
+ for (const source of composedModules(client, appPath)){
447
+ const module = graph.module(absolute(source));
448
+ types_namespaceObject.traverseFast(module.file, (node)=>{
449
+ if (!types_namespaceObject.isCallExpression(node) && !types_namespaceObject.isOptionalCallExpression(node)) return;
450
+ const native = graph.evaluate(module, node.callee).some((value)=>'external' === value.kind && value.specifier === effect && value.name === factory);
451
+ if (native && !ownedBy(module, node.arguments[0])) violations.add(`${source}: must construct the client from its own API contract`);
452
+ });
257
453
  }
258
- return order;
454
+ return [
455
+ ...violations
456
+ ];
259
457
  };
260
458
  const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
261
459
  if (!exists(file)) return;
@@ -294,6 +492,14 @@ function check(options, topology) {
294
492
  })), `${file}: must default-export ${defaultExport}`);
295
493
  };
296
494
  const effect = '@modern-js/bff-effect/effect-client';
495
+ const httpApiSources = [
496
+ effect,
497
+ 'effect/http-api'
498
+ ];
499
+ const clientFactories = new Set([
500
+ 'makeEffectHttpApiClient',
501
+ 'makeEffectRpcClient'
502
+ ]);
297
503
  const appDirectories = [
298
504
  ...new Set([
299
505
  ...directories('apps'),
@@ -471,6 +677,7 @@ function check(options, topology) {
471
677
  ], [
472
678
  rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
473
679
  ], [], void 0, appPath);
680
+ if (exists(contract)) diagnostics.push(...foreignClientConstructions(client, appPath, contract, stem, rpc));
474
681
  }
475
682
  moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
476
683
  [
@@ -514,8 +721,8 @@ function check(options, topology) {
514
721
  ]);
515
722
  });
516
723
  const sourceRules = options.sourceRules ?? [];
517
- const graph = (0, external_module_graph_cjs_namespaceObject.createModuleGraph)();
518
724
  for (const file of sourceRules.length ? sourceFiles : [])if (/\.[cm]?[jt]sx?$/u.test(file)) guarded(file, ()=>{
725
+ const graph = (0, external_module_graph_cjs_namespaceObject.createModuleGraph)();
519
726
  let module;
520
727
  try {
521
728
  module = graph.module(absolute(file));
@@ -534,20 +741,178 @@ function check(options, topology) {
534
741
  for (const message of messages)diagnostics.push(`${file}: ${message}`);
535
742
  }
536
743
  });
537
- const shell = 'apps/shell-super-app';
538
- const verticals = apps.filter((app)=>app.path.startsWith('verticals/') && apiApp(app));
539
- if (exists(shell) && verticals.length) {
540
- const file = `${shell}/src/api/vertical-clients.ts`;
541
- assert(exists(file), `${file}: must aggregate vertical API clients`);
542
- if (exists(file)) guarded(file, ()=>{
543
- const ast = parseFile(file);
544
- for (const app of verticals){
545
- const specifier = `${app.package}/api/${app.api?.protocol === 'rpc' ? 'rpc-client' : 'client'}`;
546
- assert(ast?.program.body.some((item)=>types_namespaceObject.isExportNamedDeclaration(item) && item.source?.value === specifier), `${file}: must re-export ${specifier}`);
744
+ const checkShellExports = (shellPath)=>{
745
+ const packageFile = `${shellPath}/package.json`;
746
+ const manifest = JSON.parse(read(packageFile));
747
+ noPath(`${shellPath}/src/api/vertical-clients.ts`, 'Shell client registries are forbidden; import each owner client directly');
748
+ assert(!Object.hasOwn(manifest.exports ?? {}, './api/clients'), `${packageFile}: Shell ./api/clients exports are forbidden`);
749
+ const owner = external_node_fs_default().realpathSync(absolute(shellPath));
750
+ const invalidExport = ()=>assert(false, `${packageFile}: invalid Shell public source export`);
751
+ const targets = (exported)=>'string' == typeof exported ? [
752
+ exported
753
+ ] : exported && 'object' == typeof exported ? Object.values(exported).flatMap(targets) : [];
754
+ const filesUnder = (directory, seen = new Set())=>{
755
+ const real = external_node_fs_default().realpathSync(absolute(directory));
756
+ if (real !== owner && !inside(owner, real)) {
757
+ invalidExport();
758
+ return [];
547
759
  }
548
- });
760
+ if (seen.has(real)) return [];
761
+ seen.add(real);
762
+ return external_node_fs_default().readdirSync(absolute(directory), {
763
+ withFileTypes: true
764
+ }).flatMap((entry)=>{
765
+ if (ignored.has(entry.name)) return [];
766
+ const file = `${directory}/${entry.name}`;
767
+ const stat = external_node_fs_default().statSync(absolute(file));
768
+ return stat.isDirectory() ? filesUnder(file, seen) : stat.isFile() ? [
769
+ file
770
+ ] : [];
771
+ });
772
+ };
773
+ const exportedFiles = (target)=>{
774
+ if (!target.includes('*')) return [
775
+ `${shellPath}/${target.slice(2)}`
776
+ ];
777
+ const prefix = target.slice(0, target.indexOf('*'));
778
+ const pattern = new RegExp(`^${target.split('*').map((part)=>part.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&')).join('.*')}$`, 'u');
779
+ return filesUnder(`${shellPath}/${prefix.slice(2, prefix.lastIndexOf('/'))}`).filter((file)=>pattern.test(`./${external_node_path_default().posix.relative(shellPath, file)}`));
780
+ };
781
+ const ownClientPrefix = `${manifest.name}/api/`;
782
+ const foreignSpecifier = (specifier)=>/\/api\/(?:rpc-)?client$/u.test(specifier) && !specifier.startsWith(ownClientPrefix);
783
+ for (const target of targets(manifest.exports))if (/\.[cm]?[jt]sx?$/u.test(target)) {
784
+ if (!target.startsWith('./') || target.split(/[\\/]/u).includes('..')) {
785
+ invalidExport();
786
+ continue;
787
+ }
788
+ for (const candidate of exportedFiles(target)){
789
+ const file = clientExportFile(shellPath, `./${external_node_path_default().posix.relative(shellPath, candidate)}`);
790
+ if (!file) {
791
+ invalidExport();
792
+ continue;
793
+ }
794
+ assert(!exposesForeignClient(file, owner, foreignSpecifier), `${file}: Shell public exports must not expose another owner's API client`);
795
+ }
796
+ }
797
+ };
798
+ const exposesForeignClient = (entry, owner, foreignSpecifier)=>{
799
+ const graph = (0, external_module_graph_cjs_namespaceObject.createModuleGraph)();
800
+ const seenValues = new Set();
801
+ const seenExports = new Map();
802
+ const workspacePath = (module)=>normalize(external_node_path_default().relative(workspace, module.path));
803
+ const resolveSource = (from, specifier)=>resolveRelative(from, specifier) ?? ownerClients.get(specifier);
804
+ const returnExpressions = (node)=>{
805
+ if (types_namespaceObject.isReturnStatement(node)) return node.argument ? [
806
+ node.argument
807
+ ] : [];
808
+ if (types_namespaceObject.isFunction(node)) return [];
809
+ return (types_namespaceObject.VISITOR_KEYS[node.type] ?? []).flatMap((key)=>{
810
+ const value = Reflect.get(node, key);
811
+ return (Array.isArray(value) ? value : [
812
+ value
813
+ ]).flatMap((child)=>types_namespaceObject.isNode(child) ? returnExpressions(child) : []);
814
+ });
815
+ };
816
+ const nestedValues = (node)=>{
817
+ if (types_namespaceObject.isObjectExpression(node)) return node.properties.flatMap((property)=>types_namespaceObject.isObjectProperty(property) ? [
818
+ property.value
819
+ ] : types_namespaceObject.isObjectMethod(property) ? returnExpressions(property.body) : types_namespaceObject.isSpreadElement(property) ? [
820
+ property.argument
821
+ ] : []);
822
+ if (types_namespaceObject.isArrayExpression(node)) return node.elements.filter((element)=>null !== element);
823
+ if (types_namespaceObject.isFunction(node)) return types_namespaceObject.isBlockStatement(node.body) ? returnExpressions(node.body) : [
824
+ node.body
825
+ ];
826
+ return [];
827
+ };
828
+ const foreignValue = (value, from)=>{
829
+ if ('external' === value.kind) {
830
+ const source = resolveSource(workspacePath(from), value.specifier);
831
+ if (!source) return foreignSpecifier(value.specifier);
832
+ return '*' === value.name ? inspect(source) : exportedValue(source, value.name);
833
+ }
834
+ if ('unresolved' === value.kind && types_namespaceObject.isImportDeclaration(value.node) && value.node.specifiers.some((specifier)=>types_namespaceObject.isImportNamespaceSpecifier(specifier))) {
835
+ const specifier = value.node.source.value;
836
+ const source = resolveSource(workspacePath(value.module), specifier);
837
+ return source ? inspect(source) : foreignSpecifier(specifier);
838
+ }
839
+ if ('node' !== value.kind || seenValues.has(value.node)) return false;
840
+ seenValues.add(value.node);
841
+ const { node, module } = value;
842
+ if ((types_namespaceObject.isCallExpression(node) || types_namespaceObject.isOptionalCallExpression(node)) && graph.evaluate(module, node.callee).some((callee)=>'external' === callee.kind && callee.specifier === effect && clientFactories.has(callee.name))) {
843
+ const [argument] = node.arguments;
844
+ const binding = argument ? graphBinding(graph, module, argument) : void 0;
845
+ if (module.boundary !== owner || binding?.kind !== 'declaration' || binding.module.boundary !== owner) return true;
846
+ }
847
+ return nestedValues(node).some((expression)=>graph.evaluate(module, expression).some((nested)=>foreignValue(nested, module)));
848
+ };
849
+ const exportName = (node)=>types_namespaceObject.isIdentifier(node) ? node.name : node.value;
850
+ const names = (source, visited = new Set())=>{
851
+ if (visited.has(source)) return [];
852
+ visited.add(source);
853
+ return graph.module(absolute(source)).file.program.body.flatMap((statement)=>{
854
+ if (types_namespaceObject.isExportDefaultDeclaration(statement)) return [
855
+ 'default'
856
+ ];
857
+ if (!types_namespaceObject.isExportNamedDeclaration(statement) && !types_namespaceObject.isExportAllDeclaration(statement) || 'type' === statement.exportKind) return [];
858
+ if (types_namespaceObject.isExportAllDeclaration(statement)) {
859
+ const target = resolveSource(source, statement.source.value);
860
+ return target ? names(target, visited).filter((name)=>'default' !== name) : [];
861
+ }
862
+ return statement.declaration ? Object.keys(types_namespaceObject.getOuterBindingIdentifiers(statement.declaration)) : statement.specifiers.flatMap((specifier)=>types_namespaceObject.isExportSpecifier(specifier) && 'type' === specifier.exportKind ? [] : [
863
+ exportName(specifier.exported)
864
+ ]);
865
+ });
866
+ };
867
+ const exportedValue = (source, name)=>{
868
+ const visited = seenExports.get(source) ?? new Set();
869
+ if (visited.has(name)) return false;
870
+ visited.add(name);
871
+ seenExports.set(source, visited);
872
+ const module = graph.module(absolute(source));
873
+ const evaluates = (from, node)=>graph.evaluate(from, node).some((value)=>foreignValue(value, from));
874
+ const binding = graph.resolve(module, name, 'export');
875
+ if (binding?.kind === 'external') return foreignValue({
876
+ ...binding,
877
+ members: []
878
+ }, module);
879
+ if (binding?.kind === 'declaration') return evaluates(binding.module, binding.expression);
880
+ for (const statement of module.file.program.body){
881
+ if (types_namespaceObject.isExportDefaultDeclaration(statement) && 'default' === name) return evaluates(module, statement.declaration);
882
+ if (!types_namespaceObject.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
883
+ if (types_namespaceObject.isFunctionDeclaration(statement.declaration) && statement.declaration.id?.name === name) return evaluates(module, statement.declaration);
884
+ const specifier = statement.specifiers.find((item)=>exportName(item.exported) === name);
885
+ if (!specifier) continue;
886
+ if (types_namespaceObject.isExportSpecifier(specifier) && 'type' === specifier.exportKind) return false;
887
+ if (!statement.source) return types_namespaceObject.isExportSpecifier(specifier) ? evaluates(module, specifier.local) : false;
888
+ const target = resolveSource(source, statement.source.value);
889
+ if (!target) return foreignSpecifier(statement.source.value);
890
+ if (types_namespaceObject.isExportNamespaceSpecifier(specifier)) return inspect(target);
891
+ if (types_namespaceObject.isExportSpecifier(specifier)) return exportedValue(target, exportName(specifier.local));
892
+ }
893
+ return module.file.program.body.some((statement)=>{
894
+ if (!types_namespaceObject.isExportAllDeclaration(statement) || 'type' === statement.exportKind) return false;
895
+ const target = resolveSource(source, statement.source.value);
896
+ return !!target && names(target).includes(name) && exportedValue(target, name);
897
+ });
898
+ };
899
+ const inspect = (source)=>[
900
+ ...new Set(names(source))
901
+ ].some((name)=>exportedValue(source, name));
902
+ return inspect(entry);
903
+ };
904
+ const ownerClients = new Map();
905
+ for (const app of apps)if (exists(`${app.path}/package.json`)) {
906
+ const manifest = JSON.parse(read(`${app.path}/package.json`));
907
+ for (const key of [
908
+ './api/client',
909
+ './api/rpc-client'
910
+ ]){
911
+ const file = clientExportFile(app.path, manifest.exports?.[key]);
912
+ if (file) ownerClients.set(`${manifest.name}${key.slice(1)}`, file);
913
+ }
549
914
  }
550
- 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`));
915
+ for (const shell of apps.filter((app)=>'shell' === app.kind))guarded(shell.path, ()=>checkShellExports(normalize(shell.path)));
551
916
  if (exists('topology/reference-topology.json')) guarded('topology/reference-topology.json', ()=>{
552
917
  for (const vertical of JSON.parse(read('topology/reference-topology.json')).verticals ?? []){
553
918
  if (vertical.api?.runtime === 'effect') {
@@ -232,6 +232,7 @@ function parseModule(filePath, workspace) {
232
232
  const file = (0, external_source_analysis_cjs_namespaceObject.parseSource)(external_node_fs_default().readFileSync(filePath, 'utf8'), filePath);
233
233
  let scope;
234
234
  const references = new Map();
235
+ const declarations = new Map();
235
236
  const returns = new Map();
236
237
  const bareReturns = new Set();
237
238
  const functionScopes = new Map();
@@ -342,6 +343,11 @@ function parseModule(filePath, workspace) {
342
343
  ReferencedIdentifier (p) {
343
344
  references.set(p.node, p.scope.getBinding(p.node.name));
344
345
  },
346
+ BindingIdentifier (p) {
347
+ if (!types_namespaceObject.isIdentifier(p.node)) return;
348
+ const binding = p.scope.getBinding(p.node.name);
349
+ if (binding?.identifier === p.node) declarations.set(p.node, binding);
350
+ },
345
351
  Function (p) {
346
352
  functionScopes.set(p.node, p.scope);
347
353
  },
@@ -407,6 +413,7 @@ function parseModule(filePath, workspace) {
407
413
  scope,
408
414
  workspace,
409
415
  references,
416
+ declarations,
410
417
  returns,
411
418
  bareReturns,
412
419
  functionScopes,
@@ -1142,7 +1149,7 @@ function createModuleGraph() {
1142
1149
  }
1143
1150
  };
1144
1151
  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);
1145
- const resolveBinding = (module, name, scope, chain, seen)=>{
1152
+ const resolveBinding = (module, name, scope, chain, seen, lexical)=>{
1146
1153
  const key = `${scope}:${module.path}#${name}`;
1147
1154
  if (seen.has(key) || seen.size >= MAX_LOOKUPS) return;
1148
1155
  seen.add(key);
@@ -1165,7 +1172,7 @@ function createModuleGraph() {
1165
1172
  return void 0 === next ? void 0 : resolveBinding(next, imported, 'export', hops, seen);
1166
1173
  };
1167
1174
  if ('local' === scope) {
1168
- const binding = factsOf(module).scope.getBinding(name);
1175
+ const binding = lexical ?? factsOf(module).scope.getBinding(name);
1169
1176
  if (!binding || mutated(module, binding)) return;
1170
1177
  if ('module' === binding.kind) {
1171
1178
  const imported = importOf(binding);
@@ -1194,6 +1201,39 @@ function createModuleGraph() {
1194
1201
  if (void 0 !== resolved) return resolved;
1195
1202
  }
1196
1203
  };
1204
+ const resolveReference = (module, reference)=>{
1205
+ const node = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(reference, true);
1206
+ const moduleFacts = factsOf(module);
1207
+ const bindingAt = (identifier)=>moduleFacts.references.get(identifier) ?? moduleFacts.declarations.get(identifier);
1208
+ if (types_namespaceObject.isIdentifier(node)) {
1209
+ const binding = bindingAt(node);
1210
+ return binding ? resolveBinding(module, node.name, 'local', [], new Set(), binding) : void 0;
1211
+ }
1212
+ if (!types_namespaceObject.isMemberExpression(node) || node.computed || !types_namespaceObject.isIdentifier(node.object) || !types_namespaceObject.isIdentifier(node.property)) return;
1213
+ const binding = bindingAt(node.object);
1214
+ if (!binding || 'module' !== binding.kind || mutated(module, binding)) return;
1215
+ const imported = importOf(binding);
1216
+ if (imported?.name !== '*') return;
1217
+ const target = follow(module, imported.specifier);
1218
+ const chain = [
1219
+ {
1220
+ path: module.path,
1221
+ name: node.object.name
1222
+ }
1223
+ ];
1224
+ if ('external' === target) return {
1225
+ kind: 'external',
1226
+ specifier: imported.specifier,
1227
+ name: node.property.name,
1228
+ chain
1229
+ };
1230
+ return void 0 === target ? void 0 : resolveBinding(target, node.property.name, 'export', chain, new Set());
1231
+ };
1232
+ const isUnboundReference = (module, reference)=>{
1233
+ const node = (0, external_source_analysis_cjs_namespaceObject.unwrapExpression)(reference, true);
1234
+ const { references } = factsOf(module);
1235
+ return types_namespaceObject.isIdentifier(node) && references.has(node) && void 0 === references.get(node);
1236
+ };
1197
1237
  const evaluation = (walking)=>{
1198
1238
  const active = new Set();
1199
1239
  let frames = 0;
@@ -1801,6 +1841,8 @@ function createModuleGraph() {
1801
1841
  return {
1802
1842
  module: (filePath)=>load(filePath),
1803
1843
  resolve: (module, name, scope)=>resolveBinding(module, name, scope, [], new Set()),
1844
+ resolveReference,
1845
+ isUnboundReference,
1804
1846
  evaluate: (module, expression)=>evaluation(false).values(module, expression, []),
1805
1847
  reachable: (module, expression)=>evaluation(true).reachable(module, expression)
1806
1848
  };