@bleedingdev/modern-js-code-tools 3.9.0-ultramodern.9 → 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.
- package/bin/modern-api-check-files.mjs +4 -1
- package/bin/modern-api-check.mjs +1 -1
- package/bin/modern-i18n-check.mjs +4 -0
- package/dist/cjs/cli/microvertical-api-check.cjs +36 -7
- package/dist/cjs/cli/workspace-source-check.cjs +90 -17
- package/dist/cjs/microvertical-api-baseline.cjs +89 -246
- package/dist/cjs/microvertical-api-boundary.cjs +507 -64
- package/dist/cjs/microvertical-api-owner.cjs +5 -2
- package/dist/cjs/module-graph.cjs +1862 -0
- package/dist/cjs/oxlint-plugin/rules/strict-effect-api-boundaries.cjs +1 -1
- package/dist/cjs/source-analysis.cjs +108 -0
- package/dist/cjs/strict-effect-runtime.cjs +75 -57
- package/dist/esm/cli/microvertical-api-check.js +26 -7
- package/dist/esm/cli/workspace-source-check.js +86 -13
- package/dist/esm/microvertical-api-baseline.js +66 -214
- package/dist/esm/microvertical-api-boundary.js +505 -65
- package/dist/esm/microvertical-api-owner.js +5 -2
- package/dist/esm/module-graph.js +1804 -0
- package/dist/esm/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm/source-analysis.js +48 -0
- package/dist/esm/strict-effect-runtime.js +75 -56
- package/dist/esm-node/cli/microvertical-api-check.js +26 -7
- package/dist/esm-node/cli/workspace-source-check.js +86 -13
- package/dist/esm-node/microvertical-api-baseline.js +66 -214
- package/dist/esm-node/microvertical-api-boundary.js +505 -65
- package/dist/esm-node/microvertical-api-owner.js +5 -2
- package/dist/esm-node/module-graph.js +1805 -0
- package/dist/esm-node/oxlint-plugin/rules/strict-effect-api-boundaries.js +1 -1
- package/dist/esm-node/source-analysis.js +49 -0
- package/dist/esm-node/strict-effect-runtime.js +75 -56
- package/dist/types/cli/microvertical-api-check.d.ts +1 -1
- package/dist/types/cli/workspace-source-check.d.ts +1 -1
- package/dist/types/microvertical-api-baseline.d.ts +4 -5
- package/dist/types/microvertical-api-boundary.d.ts +17 -2
- package/dist/types/microvertical-api-owner.d.ts +1 -1
- package/dist/types/module-graph.d.ts +100 -0
- package/dist/types/source-analysis.d.ts +13 -0
- package/package.json +17 -13
|
@@ -2,8 +2,10 @@ import "node:module";
|
|
|
2
2
|
import node_fs from "node:fs";
|
|
3
3
|
import node_path from "node:path";
|
|
4
4
|
import { parse } from "@babel/parser";
|
|
5
|
-
import {
|
|
5
|
+
import { camelCaseStem, configuredMicroVerticalApiStem, microVerticalApiBaselineViolation, pascalCaseStem } from "./microvertical-api-baseline.js";
|
|
6
6
|
import { baselinePackage, resolveBaselinePackageDirectory } from "./microvertical-api-owner.js";
|
|
7
|
+
import { createModuleGraph } from "./module-graph.js";
|
|
8
|
+
import { SourceSyntaxError, consumerParserPlugins, unwrapExpression } from "./source-analysis.js";
|
|
7
9
|
import { createEffectApiImportResolver, strictEffectRuntimeTopologyViolation } from "./strict-effect-runtime.js";
|
|
8
10
|
import * as __rspack_external__babel_types_6e8337dd from "@babel/types";
|
|
9
11
|
const ignored = new Set([
|
|
@@ -16,17 +18,54 @@ const ignored = new Set([
|
|
|
16
18
|
'node_modules',
|
|
17
19
|
'repos'
|
|
18
20
|
]);
|
|
21
|
+
const MAX_CLOSURE_DEPTH = 128;
|
|
22
|
+
const MAX_CLOSURE_MODULES = 2048;
|
|
23
|
+
const MAX_API_HOPS = 128;
|
|
24
|
+
const MAX_API_VISITS = 2048;
|
|
25
|
+
const identitySources = [
|
|
26
|
+
'effect',
|
|
27
|
+
'effect/Function'
|
|
28
|
+
];
|
|
19
29
|
const normalize = (file)=>file.split(node_path.sep).join('/').replace(/^\.\//u, '');
|
|
20
30
|
const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
|
|
21
31
|
const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
|
|
22
32
|
function configuredApps(root, supplied) {
|
|
23
|
-
const
|
|
24
|
-
if (!Array.isArray(
|
|
33
|
+
const topology = supplied ? void 0 : JSON.parse(node_fs.readFileSync(node_path.join(root, 'topology/reference-topology.json'), 'utf8'));
|
|
34
|
+
if (topology && (!topology.shell || !Array.isArray(topology.verticals) || void 0 !== topology.shells && !Array.isArray(topology.shells))) throw new Error('topology/reference-topology.json: shell and verticals must be present');
|
|
35
|
+
const entries = topology?.shell ? [
|
|
36
|
+
topology.shell,
|
|
37
|
+
...topology.shells ?? [],
|
|
38
|
+
...topology.verticals ?? []
|
|
39
|
+
] : void 0;
|
|
40
|
+
const source = supplied ?? entries?.map((entry)=>{
|
|
41
|
+
if (!entry || 'string' != typeof entry.path || !entry.path || node_path.isAbsolute(entry.path) || entry.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have an explicit workspace path');
|
|
42
|
+
const packageFile = node_path.join(root, entry.path, 'package.json');
|
|
43
|
+
const manifest = JSON.parse(node_fs.readFileSync(packageFile, 'utf8'));
|
|
44
|
+
if ('string' != typeof manifest.name || !manifest.name || void 0 !== entry.package && entry.package !== manifest.name) throw new Error(`${entry.path}/package.json: package name must match topology`);
|
|
45
|
+
const api = entry.api;
|
|
46
|
+
const stem = api?.stem ?? api?.readiness?.endpoint?.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)\/readiness$/u)?.[1];
|
|
47
|
+
return {
|
|
48
|
+
id: entry.id,
|
|
49
|
+
kind: entry.kind,
|
|
50
|
+
path: entry.path,
|
|
51
|
+
package: manifest.name,
|
|
52
|
+
surfaceProfile: entry.surfaceProfile,
|
|
53
|
+
deliveryUnitKind: entry.deliveryUnitKind,
|
|
54
|
+
...void 0 === api ? {} : {
|
|
55
|
+
api: {
|
|
56
|
+
stem,
|
|
57
|
+
prefix: api.bff?.prefix,
|
|
58
|
+
protocol: api.protocol
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
});
|
|
63
|
+
if (!Array.isArray(source)) throw new Error('topology/reference-topology.json: shell and verticals must describe apps');
|
|
25
64
|
const paths = new Set();
|
|
26
65
|
for (const app of source){
|
|
27
|
-
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || node_path.isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('
|
|
66
|
+
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || node_path.isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have a relative workspace path');
|
|
28
67
|
const key = normalize(app.path);
|
|
29
|
-
if (paths.has(key)) throw new Error(
|
|
68
|
+
if (paths.has(key)) throw new Error(`topology/reference-topology.json: duplicate app path ${key}`);
|
|
30
69
|
paths.add(key);
|
|
31
70
|
for (const name of [
|
|
32
71
|
'id',
|
|
@@ -35,7 +74,7 @@ function configuredApps(root, supplied) {
|
|
|
35
74
|
'surfaceProfile',
|
|
36
75
|
'deliveryUnitKind'
|
|
37
76
|
])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
|
|
38
|
-
|
|
77
|
+
const allowedFields = {
|
|
39
78
|
kind: [
|
|
40
79
|
'shell',
|
|
41
80
|
'vertical'
|
|
@@ -49,7 +88,11 @@ function configuredApps(root, supplied) {
|
|
|
49
88
|
'microvertical',
|
|
50
89
|
'horizontal-remote'
|
|
51
90
|
]
|
|
52
|
-
}
|
|
91
|
+
};
|
|
92
|
+
for (const field of Object.keys(allowedFields)){
|
|
93
|
+
const value = app[field];
|
|
94
|
+
if ((void 0 !== value || 'kind' === field) && ('string' != typeof value || !allowedFields[field].includes(value))) throw new Error(`${key}: invalid ${field}`);
|
|
95
|
+
}
|
|
53
96
|
if (void 0 !== app.api) {
|
|
54
97
|
if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
|
|
55
98
|
if (void 0 !== app.api.protocol && ![
|
|
@@ -90,6 +133,9 @@ function check(options, topology) {
|
|
|
90
133
|
};
|
|
91
134
|
try {
|
|
92
135
|
const apps = configuredApps(root, options.configuredApps);
|
|
136
|
+
const workspace = node_fs.realpathSync(root);
|
|
137
|
+
const realRelative = (file)=>normalize(node_path.relative(workspace, node_fs.realpathSync(absolute(file))));
|
|
138
|
+
const inside = (directory, real)=>real.startsWith(`${directory}${node_path.sep}`);
|
|
93
139
|
const byPath = new Map(apps.map((app)=>[
|
|
94
140
|
normalize(app.path),
|
|
95
141
|
app
|
|
@@ -130,39 +176,244 @@ function check(options, topology) {
|
|
|
130
176
|
const resolveRelative = (fromFile, specifier)=>{
|
|
131
177
|
if (!/^\.\.?\//u.test(specifier)) return;
|
|
132
178
|
const base = node_path.posix.join(node_path.posix.dirname(fromFile), specifier).replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
133
|
-
|
|
179
|
+
const candidate = [
|
|
134
180
|
`${base}.ts`,
|
|
135
181
|
`${base}.tsx`,
|
|
136
182
|
`${base}/index.ts`,
|
|
137
183
|
`${base}/index.tsx`
|
|
138
|
-
].find((
|
|
184
|
+
].find((file)=>exists(file));
|
|
185
|
+
return void 0 === candidate ? void 0 : realRelative(candidate);
|
|
186
|
+
};
|
|
187
|
+
const graphBinding = (graph, module, expression, seen = new Set())=>{
|
|
188
|
+
if (seen.has(expression) || seen.size >= 128) return;
|
|
189
|
+
seen.add(expression);
|
|
190
|
+
const binding = __rspack_external__babel_types_6e8337dd.isIdentifier(expression) || __rspack_external__babel_types_6e8337dd.isMemberExpression(expression) ? graph.resolveReference(module, expression) : void 0;
|
|
191
|
+
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;
|
|
192
|
+
};
|
|
193
|
+
const clientExportFile = (appPath, exported)=>{
|
|
194
|
+
if ('string' != typeof exported || !exported.startsWith('./') || exported.split(/[\\/]/u).includes('..') || !/\.[cm]?[jt]sx?$/u.test(exported)) return;
|
|
195
|
+
const appDirectory = absolute(appPath);
|
|
196
|
+
const candidate = node_path.resolve(appDirectory, exported);
|
|
197
|
+
try {
|
|
198
|
+
const workspace = node_fs.realpathSync(root);
|
|
199
|
+
const owner = node_fs.realpathSync(appDirectory);
|
|
200
|
+
const real = node_fs.realpathSync(candidate);
|
|
201
|
+
if (!owner.startsWith(`${workspace}${node_path.sep}`) || !real.startsWith(`${owner}${node_path.sep}`) || !node_fs.statSync(real).isFile()) return;
|
|
202
|
+
} catch {
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
return normalize(node_path.relative(root, candidate));
|
|
139
206
|
};
|
|
140
|
-
const composedModules = (file)=>{
|
|
141
|
-
const
|
|
142
|
-
|
|
207
|
+
const composedModules = (file, appPath)=>{
|
|
208
|
+
const owner = node_fs.realpathSync(absolute(appPath));
|
|
209
|
+
if (!inside(owner, node_fs.realpathSync(absolute(file)))) throw new Error(`consumer module closure escapes its owner: ${file}`);
|
|
210
|
+
const entry = realRelative(file);
|
|
211
|
+
const discovered = new Set([
|
|
212
|
+
entry
|
|
213
|
+
]);
|
|
143
214
|
const queue = [
|
|
144
|
-
|
|
215
|
+
{
|
|
216
|
+
file: entry,
|
|
217
|
+
depth: 0
|
|
218
|
+
}
|
|
145
219
|
];
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
if (seen.has(current)) continue;
|
|
149
|
-
seen.add(current);
|
|
150
|
-
order.push(current);
|
|
220
|
+
let graph;
|
|
221
|
+
for (const { file: current, depth } of queue){
|
|
151
222
|
const ast = parseFile(current);
|
|
152
223
|
if (!ast) continue;
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
224
|
+
const sources = [];
|
|
225
|
+
let callsRequire = false;
|
|
226
|
+
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);
|
|
227
|
+
__rspack_external__babel_types_6e8337dd.traverseFast(ast, (node)=>{
|
|
228
|
+
if (__rspack_external__babel_types_6e8337dd.isImportExpression(node)) {
|
|
229
|
+
if (!__rspack_external__babel_types_6e8337dd.isStringLiteral(node.source)) throw new Error(`consumer module closure has nonliteral runtime dependency: ${current}`);
|
|
230
|
+
sources.push(node.source.value);
|
|
231
|
+
}
|
|
232
|
+
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), {
|
|
233
|
+
name: 'require'
|
|
234
|
+
})) callsRequire = true;
|
|
235
|
+
});
|
|
236
|
+
if (callsRequire) {
|
|
237
|
+
graph ??= createModuleGraph();
|
|
238
|
+
const module = graph.module(absolute(current));
|
|
239
|
+
__rspack_external__babel_types_6e8337dd.traverseFast(module.file, (node)=>{
|
|
240
|
+
if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) && !__rspack_external__babel_types_6e8337dd.isOptionalCallExpression(node)) return;
|
|
241
|
+
const callee = unwrapExpression(node.callee, true);
|
|
242
|
+
if (!__rspack_external__babel_types_6e8337dd.isIdentifier(callee, {
|
|
243
|
+
name: 'require'
|
|
244
|
+
}) || !graph.isUnboundReference(module, callee)) return;
|
|
245
|
+
const [argument] = node.arguments;
|
|
246
|
+
if (1 !== node.arguments.length || !__rspack_external__babel_types_6e8337dd.isStringLiteral(argument)) throw new Error(`consumer module closure has nonliteral runtime dependency: ${current}`);
|
|
247
|
+
sources.push(argument.value);
|
|
248
|
+
});
|
|
249
|
+
}
|
|
250
|
+
for (const source of sources){
|
|
251
|
+
if (!/^\.\.?\//u.test(source)) continue;
|
|
252
|
+
const resolved = resolveRelative(current, source);
|
|
253
|
+
if (void 0 === resolved) throw new Error(`consumer module closure has unresolved relative dependency: ${current} -> ${source}`);
|
|
254
|
+
if (!inside(owner, node_path.join(workspace, resolved))) throw new Error(`consumer module closure escapes its owner: ${current} -> ${source}`);
|
|
255
|
+
if (!discovered.has(resolved)) {
|
|
256
|
+
if (depth + 1 >= MAX_CLOSURE_DEPTH) throw new Error(`consumer module closure exceeds ${MAX_CLOSURE_DEPTH} import-depth analysis budget`);
|
|
257
|
+
if (discovered.size >= MAX_CLOSURE_MODULES) throw new Error(`consumer module closure exceeds ${MAX_CLOSURE_MODULES}-module analysis budget`);
|
|
258
|
+
discovered.add(resolved);
|
|
259
|
+
queue.push({
|
|
260
|
+
file: resolved,
|
|
261
|
+
depth: depth + 1
|
|
262
|
+
});
|
|
263
|
+
}
|
|
157
264
|
}
|
|
158
265
|
}
|
|
159
|
-
return
|
|
266
|
+
return queue.map((item)=>item.file);
|
|
160
267
|
};
|
|
161
|
-
const
|
|
268
|
+
const foreignClientConstructions = (client, appPath, contract, stem, rpc)=>{
|
|
269
|
+
const graph = createModuleGraph();
|
|
270
|
+
const contractModule = graph.module(absolute(contract));
|
|
271
|
+
const factory = rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient';
|
|
272
|
+
const ownExport = graph.resolve(contractModule, rpc ? `${pascalCaseStem(stem)}RpcGroup` : `${camelCaseStem(stem)}Api`, 'export');
|
|
273
|
+
const isIdentity = (value)=>'external' === value.kind && identitySources.includes(value.specifier) && 'identity' === value.name && 0 === value.members.length;
|
|
274
|
+
const identityPipe = (module, node)=>{
|
|
275
|
+
if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) || !__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) || node.callee.computed || !__rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property, {
|
|
276
|
+
name: 'pipe'
|
|
277
|
+
}) || 1 !== node.arguments.length) return false;
|
|
278
|
+
const values = graph.evaluate(module, node.arguments[0]);
|
|
279
|
+
return values.length > 0 && values.every(isIdentity);
|
|
280
|
+
};
|
|
281
|
+
const withoutIdentity = (module, value)=>{
|
|
282
|
+
let node = unwrapExpression(value, true);
|
|
283
|
+
for(let pipes = 0; identityPipe(module, node); pipes += 1){
|
|
284
|
+
if (pipes >= MAX_API_HOPS) return value;
|
|
285
|
+
node = unwrapExpression(node.callee.object, true);
|
|
286
|
+
}
|
|
287
|
+
return node;
|
|
288
|
+
};
|
|
289
|
+
const normalizedApi = (initial)=>{
|
|
290
|
+
let binding = initial;
|
|
291
|
+
const seen = new Set();
|
|
292
|
+
for(let hops = 0; hops < MAX_API_HOPS; hops += 1){
|
|
293
|
+
const expression = withoutIdentity(binding.module, binding.expression);
|
|
294
|
+
if (seen.has(expression)) break;
|
|
295
|
+
seen.add(expression);
|
|
296
|
+
if (!__rspack_external__babel_types_6e8337dd.isIdentifier(expression) && !__rspack_external__babel_types_6e8337dd.isMemberExpression(expression)) return {
|
|
297
|
+
...binding,
|
|
298
|
+
expression: expression
|
|
299
|
+
};
|
|
300
|
+
const next = graphBinding(graph, binding.module, expression);
|
|
301
|
+
if (next?.kind !== 'declaration') break;
|
|
302
|
+
binding = next;
|
|
303
|
+
}
|
|
304
|
+
};
|
|
305
|
+
const apiBinding = (module, value)=>{
|
|
306
|
+
const binding = graphBinding(graph, module, withoutIdentity(module, value));
|
|
307
|
+
return binding?.kind === 'declaration' ? normalizedApi(binding) : void 0;
|
|
308
|
+
};
|
|
309
|
+
const ownedApis = new Map();
|
|
310
|
+
const own = (binding)=>{
|
|
311
|
+
const identities = ownedApis.get(binding.module.path) ?? new Set();
|
|
312
|
+
identities.add(binding.expression);
|
|
313
|
+
ownedApis.set(binding.module.path, identities);
|
|
314
|
+
};
|
|
315
|
+
const proven = new Set();
|
|
316
|
+
const active = new Set();
|
|
317
|
+
let visits = 0;
|
|
318
|
+
const builtApi = (module, expression)=>{
|
|
319
|
+
const initial = withoutIdentity(module, expression);
|
|
320
|
+
if (active.has(initial) || active.size >= MAX_API_HOPS) return false;
|
|
321
|
+
active.add(initial);
|
|
322
|
+
try {
|
|
323
|
+
const children = [];
|
|
324
|
+
for(let node = initial;;){
|
|
325
|
+
if (++visits > MAX_API_VISITS) return false;
|
|
326
|
+
if (__rspack_external__babel_types_6e8337dd.isIdentifier(node) || __rspack_external__babel_types_6e8337dd.isMemberExpression(node)) {
|
|
327
|
+
const receiver = apiBinding(module, node);
|
|
328
|
+
if (!receiver || !builtApi(receiver.module, receiver.expression)) return false;
|
|
329
|
+
break;
|
|
330
|
+
}
|
|
331
|
+
if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node)) return false;
|
|
332
|
+
const callee = graph.evaluate(module, node.callee);
|
|
333
|
+
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])) {
|
|
334
|
+
if (1 !== node.arguments.length || !__rspack_external__babel_types_6e8337dd.isStringLiteral(node.arguments[0])) return false;
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
if (!__rspack_external__babel_types_6e8337dd.isMemberExpression(node.callee) || node.callee.computed || !__rspack_external__babel_types_6e8337dd.isIdentifier(node.callee.property)) return false;
|
|
338
|
+
const method = node.callee.property.name;
|
|
339
|
+
const count = node.arguments.length;
|
|
340
|
+
const [first] = node.arguments;
|
|
341
|
+
if ('addHttpApi' === method && 1 === count) children.push(first);
|
|
342
|
+
else if (!('add' === method && count > 0 || 'annotate' === method && 2 === count || [
|
|
343
|
+
'annotateMerge',
|
|
344
|
+
'middleware'
|
|
345
|
+
].includes(method) && 1 === count || 'prefix' === method && 1 === count && __rspack_external__babel_types_6e8337dd.isStringLiteral(first) && first.value.startsWith('/'))) return false;
|
|
346
|
+
node = withoutIdentity(module, node.callee.object);
|
|
347
|
+
}
|
|
348
|
+
return children.every((child)=>{
|
|
349
|
+
const binding = apiBinding(module, child);
|
|
350
|
+
return void 0 !== binding && collectApi(binding);
|
|
351
|
+
});
|
|
352
|
+
} finally{
|
|
353
|
+
active.delete(initial);
|
|
354
|
+
}
|
|
355
|
+
};
|
|
356
|
+
const collectApi = (binding)=>{
|
|
357
|
+
if (proven.has(binding.expression)) return true;
|
|
358
|
+
if (!builtApi(binding.module, binding.expression)) return false;
|
|
359
|
+
proven.add(binding.expression);
|
|
360
|
+
if (binding.module.boundary === contractModule.boundary) own(binding);
|
|
361
|
+
return true;
|
|
362
|
+
};
|
|
363
|
+
if (!rpc && ownExport?.kind === 'declaration') {
|
|
364
|
+
const root = normalizedApi(ownExport);
|
|
365
|
+
if (root && collectApi(root)) own(root);
|
|
366
|
+
else ownedApis.clear();
|
|
367
|
+
}
|
|
368
|
+
const immutablePipeAliases = (module, target)=>{
|
|
369
|
+
if (0 === module.mutations.length) return true;
|
|
370
|
+
let valid = true;
|
|
371
|
+
__rspack_external__babel_types_6e8337dd.traverseFast(module.file, (node)=>{
|
|
372
|
+
if (!__rspack_external__babel_types_6e8337dd.isVariableDeclarator(node) || !__rspack_external__babel_types_6e8337dd.isIdentifier(node.id) || !node.init) return;
|
|
373
|
+
let initializer = unwrapExpression(node.init, true);
|
|
374
|
+
let identityOnly = true;
|
|
375
|
+
let piped = false;
|
|
376
|
+
while(__rspack_external__babel_types_6e8337dd.isCallExpression(initializer) && __rspack_external__babel_types_6e8337dd.isMemberExpression(initializer.callee) && !initializer.callee.computed && __rspack_external__babel_types_6e8337dd.isIdentifier(initializer.callee.property, {
|
|
377
|
+
name: 'pipe'
|
|
378
|
+
})){
|
|
379
|
+
piped = true;
|
|
380
|
+
identityOnly &&= identityPipe(module, initializer);
|
|
381
|
+
initializer = unwrapExpression(initializer.callee.object, true);
|
|
382
|
+
}
|
|
383
|
+
if (!piped) return;
|
|
384
|
+
const receiver = apiBinding(module, initializer);
|
|
385
|
+
if (receiver?.module.path !== target.module.path || receiver.expression !== target.expression) return;
|
|
386
|
+
if (!identityOnly || graph.resolveReference(module, node.id)?.kind !== 'declaration') valid = false;
|
|
387
|
+
});
|
|
388
|
+
return valid;
|
|
389
|
+
};
|
|
390
|
+
const ownedBy = (module, argument)=>{
|
|
391
|
+
if (!argument) return false;
|
|
392
|
+
if (rpc) {
|
|
393
|
+
const binding = graphBinding(graph, module, argument);
|
|
394
|
+
return binding?.kind === 'declaration' && ownExport?.kind === 'declaration' && binding.module.path === ownExport.module.path && binding.expression === ownExport.expression;
|
|
395
|
+
}
|
|
396
|
+
const binding = apiBinding(module, argument);
|
|
397
|
+
return void 0 !== binding && !!ownedApis.get(binding.module.path)?.has(binding.expression) && immutablePipeAliases(module, binding);
|
|
398
|
+
};
|
|
399
|
+
const violations = new Set();
|
|
400
|
+
for (const source of composedModules(client, appPath)){
|
|
401
|
+
const module = graph.module(absolute(source));
|
|
402
|
+
__rspack_external__babel_types_6e8337dd.traverseFast(module.file, (node)=>{
|
|
403
|
+
if (!__rspack_external__babel_types_6e8337dd.isCallExpression(node) && !__rspack_external__babel_types_6e8337dd.isOptionalCallExpression(node)) return;
|
|
404
|
+
const native = graph.evaluate(module, node.callee).some((value)=>'external' === value.kind && value.specifier === effect && value.name === factory);
|
|
405
|
+
if (native && !ownedBy(module, node.arguments[0])) violations.add(`${source}: must construct the client from its own API contract`);
|
|
406
|
+
});
|
|
407
|
+
}
|
|
408
|
+
return [
|
|
409
|
+
...violations
|
|
410
|
+
];
|
|
411
|
+
};
|
|
412
|
+
const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
|
|
162
413
|
if (!exists(file)) return;
|
|
163
414
|
const ast = parseFile(file);
|
|
164
415
|
if (!ast) return;
|
|
165
|
-
const shapeFiles =
|
|
416
|
+
const shapeFiles = composedAppPath ? composedModules(file, composedAppPath) : [
|
|
166
417
|
file
|
|
167
418
|
];
|
|
168
419
|
const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
|
|
@@ -195,6 +446,14 @@ function check(options, topology) {
|
|
|
195
446
|
})), `${file}: must default-export ${defaultExport}`);
|
|
196
447
|
};
|
|
197
448
|
const effect = '@modern-js/bff-effect/effect-client';
|
|
449
|
+
const httpApiSources = [
|
|
450
|
+
effect,
|
|
451
|
+
'effect/http-api'
|
|
452
|
+
];
|
|
453
|
+
const clientFactories = new Set([
|
|
454
|
+
'makeEffectHttpApiClient',
|
|
455
|
+
'makeEffectRpcClient'
|
|
456
|
+
]);
|
|
198
457
|
const appDirectories = [
|
|
199
458
|
...new Set([
|
|
200
459
|
...directories('apps'),
|
|
@@ -255,7 +514,6 @@ function check(options, topology) {
|
|
|
255
514
|
if (violation) diagnostics.push(`${file}: ${violation}`);
|
|
256
515
|
}
|
|
257
516
|
});
|
|
258
|
-
let owner;
|
|
259
517
|
for (const appPath of appDirectories)guarded(appPath, ()=>{
|
|
260
518
|
const app = byPath.get(appPath);
|
|
261
519
|
const emits = app ? apiApp(app) || 'vertical' !== app.kind && void 0 !== app.api : appPath.startsWith('verticals/') || exists(`${appPath}/api/index.ts`) || exists(`${appPath}/shared/api.ts`);
|
|
@@ -272,19 +530,25 @@ function check(options, topology) {
|
|
|
272
530
|
}
|
|
273
531
|
return;
|
|
274
532
|
}
|
|
275
|
-
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in
|
|
533
|
+
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in topology/reference-topology.json`);
|
|
276
534
|
const stem = app?.api?.stem ?? node_path.posix.basename(appPath);
|
|
277
535
|
const rpc = app?.api?.protocol === 'rpc';
|
|
278
536
|
const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
|
|
279
|
-
const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
|
|
280
537
|
const entry = `${appPath}/api/index.ts`;
|
|
281
538
|
for (const file of [
|
|
282
539
|
entry,
|
|
283
|
-
contract
|
|
284
|
-
client
|
|
540
|
+
contract
|
|
285
541
|
])assert(exists(file), `${file}: required API surface is missing`);
|
|
542
|
+
const packageFile = `${appPath}/package.json`;
|
|
543
|
+
assert(exists(packageFile), `${packageFile}: required API package is missing`);
|
|
544
|
+
if (!exists(packageFile)) return;
|
|
545
|
+
const manifest = JSON.parse(read(packageFile));
|
|
546
|
+
assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
|
|
547
|
+
const clientKey = `./api/${rpc ? 'rpc-client' : 'client'}`;
|
|
548
|
+
const client = clientExportFile(appPath, manifest.exports?.[clientKey]);
|
|
549
|
+
assert(void 0 !== client, `${packageFile}: invalid API client export`);
|
|
550
|
+
assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
|
|
286
551
|
noPath(`${appPath}/shared/${rpc ? 'api' : 'rpc'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} contract`);
|
|
287
|
-
noPath(`${appPath}/src/api/${stem}-${rpc ? 'client' : 'rpc-client'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} client`);
|
|
288
552
|
if (rpc) {
|
|
289
553
|
moduleShape(entry, [
|
|
290
554
|
[
|
|
@@ -315,7 +579,7 @@ function check(options, topology) {
|
|
|
315
579
|
], 'apiRuntime');
|
|
316
580
|
moduleShape(contract, [
|
|
317
581
|
[
|
|
318
|
-
'effect/
|
|
582
|
+
'effect/rpc',
|
|
319
583
|
[
|
|
320
584
|
'Rpc',
|
|
321
585
|
'RpcGroup'
|
|
@@ -333,7 +597,7 @@ function check(options, topology) {
|
|
|
333
597
|
'Schema.Struct'
|
|
334
598
|
]);
|
|
335
599
|
} else if (exists(contract)) {
|
|
336
|
-
owner
|
|
600
|
+
const owner = options.baselinePackageDirectory ?? resolveBaselinePackageDirectory(absolute(contract));
|
|
337
601
|
const apiPrefix = app?.api?.prefix ?? `/${stem}-api`;
|
|
338
602
|
const basePath = `${apiPrefix}/${stem}`;
|
|
339
603
|
const expectation = {
|
|
@@ -350,21 +614,25 @@ function check(options, topology) {
|
|
|
350
614
|
const violation = microVerticalApiBaselineViolation(stem, absolute(contract), expectation);
|
|
351
615
|
if (violation) diagnostics.push(`${contract}: ${violation}`);
|
|
352
616
|
}
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
617
|
+
if (client) {
|
|
618
|
+
const relativeContract = node_path.posix.relative(node_path.posix.dirname(client), contract);
|
|
619
|
+
moduleShape(client, [
|
|
620
|
+
[
|
|
621
|
+
effect,
|
|
622
|
+
[
|
|
623
|
+
'Effect',
|
|
624
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
625
|
+
]
|
|
626
|
+
],
|
|
356
627
|
[
|
|
357
|
-
'
|
|
358
|
-
|
|
628
|
+
relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
|
|
629
|
+
[]
|
|
359
630
|
]
|
|
360
|
-
],
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
], [
|
|
366
|
-
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
367
|
-
], [], void 0, true);
|
|
631
|
+
], [
|
|
632
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
633
|
+
], [], void 0, appPath);
|
|
634
|
+
if (exists(contract)) diagnostics.push(...foreignClientConstructions(client, appPath, contract, stem, rpc));
|
|
635
|
+
}
|
|
368
636
|
moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
|
|
369
637
|
[
|
|
370
638
|
/\bbackendFederationContract\b/u,
|
|
@@ -405,28 +673,200 @@ function check(options, topology) {
|
|
|
405
673
|
'must enable strictEffectApproach'
|
|
406
674
|
]
|
|
407
675
|
]);
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
676
|
+
});
|
|
677
|
+
const sourceRules = options.sourceRules ?? [];
|
|
678
|
+
for (const file of sourceRules.length ? sourceFiles : [])if (/\.[cm]?[jt]sx?$/u.test(file)) guarded(file, ()=>{
|
|
679
|
+
const graph = createModuleGraph();
|
|
680
|
+
let module;
|
|
681
|
+
try {
|
|
682
|
+
module = graph.module(absolute(file));
|
|
683
|
+
} catch (error) {
|
|
684
|
+
if (!(error instanceof SourceSyntaxError)) throw error;
|
|
685
|
+
diagnostics.push(`${file}: source rules need parseable source (${error.message})`);
|
|
686
|
+
return;
|
|
687
|
+
}
|
|
688
|
+
for (const rule of sourceRules){
|
|
689
|
+
const messages = rule({
|
|
690
|
+
file,
|
|
691
|
+
module,
|
|
692
|
+
graph
|
|
693
|
+
});
|
|
694
|
+
if (!Array.isArray(messages) || !messages.every((message)=>'string' == typeof message)) throw new Error(`rule ${rule.name || 'anonymous'} must return an array of violation messages (return [] when the file passes)`);
|
|
695
|
+
for (const message of messages)diagnostics.push(`${file}: ${message}`);
|
|
414
696
|
}
|
|
415
697
|
});
|
|
416
|
-
const
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
assert(
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
698
|
+
const checkShellExports = (shellPath)=>{
|
|
699
|
+
const packageFile = `${shellPath}/package.json`;
|
|
700
|
+
const manifest = JSON.parse(read(packageFile));
|
|
701
|
+
noPath(`${shellPath}/src/api/vertical-clients.ts`, 'Shell client registries are forbidden; import each owner client directly');
|
|
702
|
+
assert(!Object.hasOwn(manifest.exports ?? {}, './api/clients'), `${packageFile}: Shell ./api/clients exports are forbidden`);
|
|
703
|
+
const owner = node_fs.realpathSync(absolute(shellPath));
|
|
704
|
+
const invalidExport = ()=>assert(false, `${packageFile}: invalid Shell public source export`);
|
|
705
|
+
const targets = (exported)=>'string' == typeof exported ? [
|
|
706
|
+
exported
|
|
707
|
+
] : exported && 'object' == typeof exported ? Object.values(exported).flatMap(targets) : [];
|
|
708
|
+
const filesUnder = (directory, seen = new Set())=>{
|
|
709
|
+
const real = node_fs.realpathSync(absolute(directory));
|
|
710
|
+
if (real !== owner && !inside(owner, real)) {
|
|
711
|
+
invalidExport();
|
|
712
|
+
return [];
|
|
426
713
|
}
|
|
427
|
-
|
|
714
|
+
if (seen.has(real)) return [];
|
|
715
|
+
seen.add(real);
|
|
716
|
+
return node_fs.readdirSync(absolute(directory), {
|
|
717
|
+
withFileTypes: true
|
|
718
|
+
}).flatMap((entry)=>{
|
|
719
|
+
if (ignored.has(entry.name)) return [];
|
|
720
|
+
const file = `${directory}/${entry.name}`;
|
|
721
|
+
const stat = node_fs.statSync(absolute(file));
|
|
722
|
+
return stat.isDirectory() ? filesUnder(file, seen) : stat.isFile() ? [
|
|
723
|
+
file
|
|
724
|
+
] : [];
|
|
725
|
+
});
|
|
726
|
+
};
|
|
727
|
+
const exportedFiles = (target)=>{
|
|
728
|
+
if (!target.includes('*')) return [
|
|
729
|
+
`${shellPath}/${target.slice(2)}`
|
|
730
|
+
];
|
|
731
|
+
const prefix = target.slice(0, target.indexOf('*'));
|
|
732
|
+
const pattern = new RegExp(`^${target.split('*').map((part)=>part.replaceAll(/[.*+?^${}()|[\]\\]/gu, '\\$&')).join('.*')}$`, 'u');
|
|
733
|
+
return filesUnder(`${shellPath}/${prefix.slice(2, prefix.lastIndexOf('/'))}`).filter((file)=>pattern.test(`./${node_path.posix.relative(shellPath, file)}`));
|
|
734
|
+
};
|
|
735
|
+
const ownClientPrefix = `${manifest.name}/api/`;
|
|
736
|
+
const foreignSpecifier = (specifier)=>/\/api\/(?:rpc-)?client$/u.test(specifier) && !specifier.startsWith(ownClientPrefix);
|
|
737
|
+
for (const target of targets(manifest.exports))if (/\.[cm]?[jt]sx?$/u.test(target)) {
|
|
738
|
+
if (!target.startsWith('./') || target.split(/[\\/]/u).includes('..')) {
|
|
739
|
+
invalidExport();
|
|
740
|
+
continue;
|
|
741
|
+
}
|
|
742
|
+
for (const candidate of exportedFiles(target)){
|
|
743
|
+
const file = clientExportFile(shellPath, `./${node_path.posix.relative(shellPath, candidate)}`);
|
|
744
|
+
if (!file) {
|
|
745
|
+
invalidExport();
|
|
746
|
+
continue;
|
|
747
|
+
}
|
|
748
|
+
assert(!exposesForeignClient(file, owner, foreignSpecifier), `${file}: Shell public exports must not expose another owner's API client`);
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
};
|
|
752
|
+
const exposesForeignClient = (entry, owner, foreignSpecifier)=>{
|
|
753
|
+
const graph = createModuleGraph();
|
|
754
|
+
const seenValues = new Set();
|
|
755
|
+
const seenExports = new Map();
|
|
756
|
+
const workspacePath = (module)=>normalize(node_path.relative(workspace, module.path));
|
|
757
|
+
const resolveSource = (from, specifier)=>resolveRelative(from, specifier) ?? ownerClients.get(specifier);
|
|
758
|
+
const returnExpressions = (node)=>{
|
|
759
|
+
if (__rspack_external__babel_types_6e8337dd.isReturnStatement(node)) return node.argument ? [
|
|
760
|
+
node.argument
|
|
761
|
+
] : [];
|
|
762
|
+
if (__rspack_external__babel_types_6e8337dd.isFunction(node)) return [];
|
|
763
|
+
return (__rspack_external__babel_types_6e8337dd.VISITOR_KEYS[node.type] ?? []).flatMap((key)=>{
|
|
764
|
+
const value = Reflect.get(node, key);
|
|
765
|
+
return (Array.isArray(value) ? value : [
|
|
766
|
+
value
|
|
767
|
+
]).flatMap((child)=>__rspack_external__babel_types_6e8337dd.isNode(child) ? returnExpressions(child) : []);
|
|
768
|
+
});
|
|
769
|
+
};
|
|
770
|
+
const nestedValues = (node)=>{
|
|
771
|
+
if (__rspack_external__babel_types_6e8337dd.isObjectExpression(node)) return node.properties.flatMap((property)=>__rspack_external__babel_types_6e8337dd.isObjectProperty(property) ? [
|
|
772
|
+
property.value
|
|
773
|
+
] : __rspack_external__babel_types_6e8337dd.isObjectMethod(property) ? returnExpressions(property.body) : __rspack_external__babel_types_6e8337dd.isSpreadElement(property) ? [
|
|
774
|
+
property.argument
|
|
775
|
+
] : []);
|
|
776
|
+
if (__rspack_external__babel_types_6e8337dd.isArrayExpression(node)) return node.elements.filter((element)=>null !== element);
|
|
777
|
+
if (__rspack_external__babel_types_6e8337dd.isFunction(node)) return __rspack_external__babel_types_6e8337dd.isBlockStatement(node.body) ? returnExpressions(node.body) : [
|
|
778
|
+
node.body
|
|
779
|
+
];
|
|
780
|
+
return [];
|
|
781
|
+
};
|
|
782
|
+
const foreignValue = (value, from)=>{
|
|
783
|
+
if ('external' === value.kind) {
|
|
784
|
+
const source = resolveSource(workspacePath(from), value.specifier);
|
|
785
|
+
if (!source) return foreignSpecifier(value.specifier);
|
|
786
|
+
return '*' === value.name ? inspect(source) : exportedValue(source, value.name);
|
|
787
|
+
}
|
|
788
|
+
if ('unresolved' === value.kind && __rspack_external__babel_types_6e8337dd.isImportDeclaration(value.node) && value.node.specifiers.some((specifier)=>__rspack_external__babel_types_6e8337dd.isImportNamespaceSpecifier(specifier))) {
|
|
789
|
+
const specifier = value.node.source.value;
|
|
790
|
+
const source = resolveSource(workspacePath(value.module), specifier);
|
|
791
|
+
return source ? inspect(source) : foreignSpecifier(specifier);
|
|
792
|
+
}
|
|
793
|
+
if ('node' !== value.kind || seenValues.has(value.node)) return false;
|
|
794
|
+
seenValues.add(value.node);
|
|
795
|
+
const { node, module } = value;
|
|
796
|
+
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))) {
|
|
797
|
+
const [argument] = node.arguments;
|
|
798
|
+
const binding = argument ? graphBinding(graph, module, argument) : void 0;
|
|
799
|
+
if (module.boundary !== owner || binding?.kind !== 'declaration' || binding.module.boundary !== owner) return true;
|
|
800
|
+
}
|
|
801
|
+
return nestedValues(node).some((expression)=>graph.evaluate(module, expression).some((nested)=>foreignValue(nested, module)));
|
|
802
|
+
};
|
|
803
|
+
const exportName = (node)=>__rspack_external__babel_types_6e8337dd.isIdentifier(node) ? node.name : node.value;
|
|
804
|
+
const names = (source, visited = new Set())=>{
|
|
805
|
+
if (visited.has(source)) return [];
|
|
806
|
+
visited.add(source);
|
|
807
|
+
return graph.module(absolute(source)).file.program.body.flatMap((statement)=>{
|
|
808
|
+
if (__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement)) return [
|
|
809
|
+
'default'
|
|
810
|
+
];
|
|
811
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) && !__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) return [];
|
|
812
|
+
if (__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement)) {
|
|
813
|
+
const target = resolveSource(source, statement.source.value);
|
|
814
|
+
return target ? names(target, visited).filter((name)=>'default' !== name) : [];
|
|
815
|
+
}
|
|
816
|
+
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 ? [] : [
|
|
817
|
+
exportName(specifier.exported)
|
|
818
|
+
]);
|
|
819
|
+
});
|
|
820
|
+
};
|
|
821
|
+
const exportedValue = (source, name)=>{
|
|
822
|
+
const visited = seenExports.get(source) ?? new Set();
|
|
823
|
+
if (visited.has(name)) return false;
|
|
824
|
+
visited.add(name);
|
|
825
|
+
seenExports.set(source, visited);
|
|
826
|
+
const module = graph.module(absolute(source));
|
|
827
|
+
const evaluates = (from, node)=>graph.evaluate(from, node).some((value)=>foreignValue(value, from));
|
|
828
|
+
const binding = graph.resolve(module, name, 'export');
|
|
829
|
+
if (binding?.kind === 'external') return foreignValue({
|
|
830
|
+
...binding,
|
|
831
|
+
members: []
|
|
832
|
+
}, module);
|
|
833
|
+
if (binding?.kind === 'declaration') return evaluates(binding.module, binding.expression);
|
|
834
|
+
for (const statement of module.file.program.body){
|
|
835
|
+
if (__rspack_external__babel_types_6e8337dd.isExportDefaultDeclaration(statement) && 'default' === name) return evaluates(module, statement.declaration);
|
|
836
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportNamedDeclaration(statement) || 'type' === statement.exportKind) continue;
|
|
837
|
+
if (__rspack_external__babel_types_6e8337dd.isFunctionDeclaration(statement.declaration) && statement.declaration.id?.name === name) return evaluates(module, statement.declaration);
|
|
838
|
+
const specifier = statement.specifiers.find((item)=>exportName(item.exported) === name);
|
|
839
|
+
if (!specifier) continue;
|
|
840
|
+
if (__rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier) && 'type' === specifier.exportKind) return false;
|
|
841
|
+
if (!statement.source) return __rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier) ? evaluates(module, specifier.local) : false;
|
|
842
|
+
const target = resolveSource(source, statement.source.value);
|
|
843
|
+
if (!target) return foreignSpecifier(statement.source.value);
|
|
844
|
+
if (__rspack_external__babel_types_6e8337dd.isExportNamespaceSpecifier(specifier)) return inspect(target);
|
|
845
|
+
if (__rspack_external__babel_types_6e8337dd.isExportSpecifier(specifier)) return exportedValue(target, exportName(specifier.local));
|
|
846
|
+
}
|
|
847
|
+
return module.file.program.body.some((statement)=>{
|
|
848
|
+
if (!__rspack_external__babel_types_6e8337dd.isExportAllDeclaration(statement) || 'type' === statement.exportKind) return false;
|
|
849
|
+
const target = resolveSource(source, statement.source.value);
|
|
850
|
+
return !!target && names(target).includes(name) && exportedValue(target, name);
|
|
851
|
+
});
|
|
852
|
+
};
|
|
853
|
+
const inspect = (source)=>[
|
|
854
|
+
...new Set(names(source))
|
|
855
|
+
].some((name)=>exportedValue(source, name));
|
|
856
|
+
return inspect(entry);
|
|
857
|
+
};
|
|
858
|
+
const ownerClients = new Map();
|
|
859
|
+
for (const app of apps)if (exists(`${app.path}/package.json`)) {
|
|
860
|
+
const manifest = JSON.parse(read(`${app.path}/package.json`));
|
|
861
|
+
for (const key of [
|
|
862
|
+
'./api/client',
|
|
863
|
+
'./api/rpc-client'
|
|
864
|
+
]){
|
|
865
|
+
const file = clientExportFile(app.path, manifest.exports?.[key]);
|
|
866
|
+
if (file) ownerClients.set(`${manifest.name}${key.slice(1)}`, file);
|
|
867
|
+
}
|
|
428
868
|
}
|
|
429
|
-
|
|
869
|
+
for (const shell of apps.filter((app)=>'shell' === app.kind))guarded(shell.path, ()=>checkShellExports(normalize(shell.path)));
|
|
430
870
|
if (exists('topology/reference-topology.json')) guarded('topology/reference-topology.json', ()=>{
|
|
431
871
|
for (const vertical of JSON.parse(read('topology/reference-topology.json')).verticals ?? []){
|
|
432
872
|
if (vertical.api?.runtime === 'effect') {
|
|
@@ -447,4 +887,4 @@ function check(options, topology) {
|
|
|
447
887
|
}
|
|
448
888
|
const checkMicroVerticalApiBoundaries = (options)=>check(options, true);
|
|
449
889
|
const checkMicroVerticalApiConsumerFiles = (options)=>check(options, false);
|
|
450
|
-
export { checkMicroVerticalApiBoundaries, checkMicroVerticalApiConsumerFiles, configuredMicroVerticalApiStem, microVerticalApiBaselineViolation };
|
|
890
|
+
export { checkMicroVerticalApiBoundaries, checkMicroVerticalApiConsumerFiles, configuredMicroVerticalApiStem, createModuleGraph, microVerticalApiBaselineViolation };
|