@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.
- package/dist/cjs/microvertical-api-boundary.cjs +242 -32
- package/dist/cjs/module-graph.cjs +44 -2
- package/dist/cjs/strict-effect-runtime.cjs +59 -6
- package/dist/esm/microvertical-api-boundary.js +243 -33
- package/dist/esm/module-graph.js +44 -2
- package/dist/esm/strict-effect-runtime.js +59 -6
- package/dist/esm-node/microvertical-api-boundary.js +243 -33
- package/dist/esm-node/module-graph.js +44 -2
- package/dist/esm-node/strict-effect-runtime.js +59 -6
- package/dist/types/microvertical-api-boundary.d.ts +3 -2
- package/dist/types/module-graph.d.ts +8 -0
- package/package.json +1 -1
|
@@ -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
|
-
|
|
173
|
+
const candidate = [
|
|
169
174
|
`${base}.ts`,
|
|
170
175
|
`${base}.tsx`,
|
|
171
176
|
`${base}/index.ts`,
|
|
172
177
|
`${base}/index.tsx`
|
|
173
|
-
].find((
|
|
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
|
|
191
|
-
|
|
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
|
-
|
|
209
|
+
{
|
|
210
|
+
file: entry,
|
|
211
|
+
depth: 0
|
|
212
|
+
}
|
|
194
213
|
];
|
|
195
|
-
|
|
196
|
-
|
|
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
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (
|
|
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
|
|
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
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
assert(
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
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
|
-
|
|
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
|
-
|
|
59
|
-
|
|
60
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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,
|