@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
|
@@ -40,6 +40,7 @@ __webpack_require__.d(__webpack_exports__, {
|
|
|
40
40
|
checkMicroVerticalApiBoundaries: ()=>checkMicroVerticalApiBoundaries,
|
|
41
41
|
checkMicroVerticalApiConsumerFiles: ()=>checkMicroVerticalApiConsumerFiles,
|
|
42
42
|
configuredMicroVerticalApiStem: ()=>external_microvertical_api_baseline_cjs_namespaceObject.configuredMicroVerticalApiStem,
|
|
43
|
+
createModuleGraph: ()=>external_module_graph_cjs_namespaceObject.createModuleGraph,
|
|
43
44
|
microVerticalApiBaselineViolation: ()=>external_microvertical_api_baseline_cjs_namespaceObject.microVerticalApiBaselineViolation
|
|
44
45
|
});
|
|
45
46
|
const external_node_fs_namespaceObject = require("node:fs");
|
|
@@ -50,6 +51,8 @@ const parser_namespaceObject = require("@babel/parser");
|
|
|
50
51
|
const types_namespaceObject = require("@babel/types");
|
|
51
52
|
const external_microvertical_api_baseline_cjs_namespaceObject = require("./microvertical-api-baseline.cjs");
|
|
52
53
|
const external_microvertical_api_owner_cjs_namespaceObject = require("./microvertical-api-owner.cjs");
|
|
54
|
+
const external_module_graph_cjs_namespaceObject = require("./module-graph.cjs");
|
|
55
|
+
const external_source_analysis_cjs_namespaceObject = require("./source-analysis.cjs");
|
|
53
56
|
const external_strict_effect_runtime_cjs_namespaceObject = require("./strict-effect-runtime.cjs");
|
|
54
57
|
const ignored = new Set([
|
|
55
58
|
'.git',
|
|
@@ -61,17 +64,54 @@ const ignored = new Set([
|
|
|
61
64
|
'node_modules',
|
|
62
65
|
'repos'
|
|
63
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
|
+
];
|
|
64
75
|
const normalize = (file)=>file.split(external_node_path_default().sep).join('/').replace(/^\.\//u, '');
|
|
65
76
|
const errorMessage = (error)=>error instanceof Error ? error.message : String(error);
|
|
66
77
|
const apiApp = (app)=>'vertical' === app.kind && 'ui-only' !== app.surfaceProfile && 'horizontal-remote' !== app.deliveryUnitKind;
|
|
67
78
|
function configuredApps(root, supplied) {
|
|
68
|
-
const
|
|
69
|
-
if (!Array.isArray(
|
|
79
|
+
const topology = supplied ? void 0 : JSON.parse(external_node_fs_default().readFileSync(external_node_path_default().join(root, 'topology/reference-topology.json'), 'utf8'));
|
|
80
|
+
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');
|
|
81
|
+
const entries = topology?.shell ? [
|
|
82
|
+
topology.shell,
|
|
83
|
+
...topology.shells ?? [],
|
|
84
|
+
...topology.verticals ?? []
|
|
85
|
+
] : void 0;
|
|
86
|
+
const source = supplied ?? entries?.map((entry)=>{
|
|
87
|
+
if (!entry || 'string' != typeof entry.path || !entry.path || external_node_path_default().isAbsolute(entry.path) || entry.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have an explicit workspace path');
|
|
88
|
+
const packageFile = external_node_path_default().join(root, entry.path, 'package.json');
|
|
89
|
+
const manifest = JSON.parse(external_node_fs_default().readFileSync(packageFile, 'utf8'));
|
|
90
|
+
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`);
|
|
91
|
+
const api = entry.api;
|
|
92
|
+
const stem = api?.stem ?? api?.readiness?.endpoint?.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)\/readiness$/u)?.[1];
|
|
93
|
+
return {
|
|
94
|
+
id: entry.id,
|
|
95
|
+
kind: entry.kind,
|
|
96
|
+
path: entry.path,
|
|
97
|
+
package: manifest.name,
|
|
98
|
+
surfaceProfile: entry.surfaceProfile,
|
|
99
|
+
deliveryUnitKind: entry.deliveryUnitKind,
|
|
100
|
+
...void 0 === api ? {} : {
|
|
101
|
+
api: {
|
|
102
|
+
stem,
|
|
103
|
+
prefix: api.bff?.prefix,
|
|
104
|
+
protocol: api.protocol
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
});
|
|
109
|
+
if (!Array.isArray(source)) throw new Error('topology/reference-topology.json: shell and verticals must describe apps');
|
|
70
110
|
const paths = new Set();
|
|
71
111
|
for (const app of source){
|
|
72
|
-
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || external_node_path_default().isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('
|
|
112
|
+
if (!app || 'object' != typeof app || 'string' != typeof app.path || !app.path || external_node_path_default().isAbsolute(app.path) || app.path.split(/[\\/]/u).includes('..')) throw new Error('topology/reference-topology.json: each app must have a relative workspace path');
|
|
73
113
|
const key = normalize(app.path);
|
|
74
|
-
if (paths.has(key)) throw new Error(
|
|
114
|
+
if (paths.has(key)) throw new Error(`topology/reference-topology.json: duplicate app path ${key}`);
|
|
75
115
|
paths.add(key);
|
|
76
116
|
for (const name of [
|
|
77
117
|
'id',
|
|
@@ -80,7 +120,7 @@ function configuredApps(root, supplied) {
|
|
|
80
120
|
'surfaceProfile',
|
|
81
121
|
'deliveryUnitKind'
|
|
82
122
|
])if (void 0 !== app[name] && 'string' != typeof app[name]) throw new Error(`${key}: ${name} must be a string`);
|
|
83
|
-
|
|
123
|
+
const allowedFields = {
|
|
84
124
|
kind: [
|
|
85
125
|
'shell',
|
|
86
126
|
'vertical'
|
|
@@ -94,7 +134,11 @@ function configuredApps(root, supplied) {
|
|
|
94
134
|
'microvertical',
|
|
95
135
|
'horizontal-remote'
|
|
96
136
|
]
|
|
97
|
-
}
|
|
137
|
+
};
|
|
138
|
+
for (const field of Object.keys(allowedFields)){
|
|
139
|
+
const value = app[field];
|
|
140
|
+
if ((void 0 !== value || 'kind' === field) && ('string' != typeof value || !allowedFields[field].includes(value))) throw new Error(`${key}: invalid ${field}`);
|
|
141
|
+
}
|
|
98
142
|
if (void 0 !== app.api) {
|
|
99
143
|
if (!app.api || 'object' != typeof app.api || Array.isArray(app.api)) throw new Error(`${key}: api must be an object`);
|
|
100
144
|
if (void 0 !== app.api.protocol && ![
|
|
@@ -135,6 +179,9 @@ function check(options, topology) {
|
|
|
135
179
|
};
|
|
136
180
|
try {
|
|
137
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}`);
|
|
138
185
|
const byPath = new Map(apps.map((app)=>[
|
|
139
186
|
normalize(app.path),
|
|
140
187
|
app
|
|
@@ -159,7 +206,7 @@ function check(options, topology) {
|
|
|
159
206
|
const ast = (0, parser_namespaceObject.parse)(read(file), {
|
|
160
207
|
sourceType: 'module',
|
|
161
208
|
sourceFilename: file,
|
|
162
|
-
plugins: (0,
|
|
209
|
+
plugins: (0, external_source_analysis_cjs_namespaceObject.consumerParserPlugins)(file)
|
|
163
210
|
});
|
|
164
211
|
parsed.set(file, ast);
|
|
165
212
|
return ast;
|
|
@@ -175,39 +222,244 @@ function check(options, topology) {
|
|
|
175
222
|
const resolveRelative = (fromFile, specifier)=>{
|
|
176
223
|
if (!/^\.\.?\//u.test(specifier)) return;
|
|
177
224
|
const base = external_node_path_default().posix.join(external_node_path_default().posix.dirname(fromFile), specifier).replace(/\.(?:[cm]?[jt]sx?)$/u, '');
|
|
178
|
-
|
|
225
|
+
const candidate = [
|
|
179
226
|
`${base}.ts`,
|
|
180
227
|
`${base}.tsx`,
|
|
181
228
|
`${base}/index.ts`,
|
|
182
229
|
`${base}/index.tsx`
|
|
183
|
-
].find((
|
|
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;
|
|
238
|
+
};
|
|
239
|
+
const clientExportFile = (appPath, exported)=>{
|
|
240
|
+
if ('string' != typeof exported || !exported.startsWith('./') || exported.split(/[\\/]/u).includes('..') || !/\.[cm]?[jt]sx?$/u.test(exported)) return;
|
|
241
|
+
const appDirectory = absolute(appPath);
|
|
242
|
+
const candidate = external_node_path_default().resolve(appDirectory, exported);
|
|
243
|
+
try {
|
|
244
|
+
const workspace = external_node_fs_default().realpathSync(root);
|
|
245
|
+
const owner = external_node_fs_default().realpathSync(appDirectory);
|
|
246
|
+
const real = external_node_fs_default().realpathSync(candidate);
|
|
247
|
+
if (!owner.startsWith(`${workspace}${external_node_path_default().sep}`) || !real.startsWith(`${owner}${external_node_path_default().sep}`) || !external_node_fs_default().statSync(real).isFile()) return;
|
|
248
|
+
} catch {
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
return normalize(external_node_path_default().relative(root, candidate));
|
|
184
252
|
};
|
|
185
|
-
const composedModules = (file)=>{
|
|
186
|
-
const
|
|
187
|
-
|
|
253
|
+
const composedModules = (file, appPath)=>{
|
|
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
|
+
]);
|
|
188
260
|
const queue = [
|
|
189
|
-
|
|
261
|
+
{
|
|
262
|
+
file: entry,
|
|
263
|
+
depth: 0
|
|
264
|
+
}
|
|
190
265
|
];
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
if (seen.has(current)) continue;
|
|
194
|
-
seen.add(current);
|
|
195
|
-
order.push(current);
|
|
266
|
+
let graph;
|
|
267
|
+
for (const { file: current, depth } of queue){
|
|
196
268
|
const ast = parseFile(current);
|
|
197
269
|
if (!ast) continue;
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
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);
|
|
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);
|
|
202
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();
|
|
203
413
|
}
|
|
204
|
-
|
|
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
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return [
|
|
455
|
+
...violations
|
|
456
|
+
];
|
|
205
457
|
};
|
|
206
|
-
const moduleShape = (file, imports, calls, patterns = [], defaultExport,
|
|
458
|
+
const moduleShape = (file, imports, calls, patterns = [], defaultExport, composedAppPath)=>{
|
|
207
459
|
if (!exists(file)) return;
|
|
208
460
|
const ast = parseFile(file);
|
|
209
461
|
if (!ast) return;
|
|
210
|
-
const shapeFiles =
|
|
462
|
+
const shapeFiles = composedAppPath ? composedModules(file, composedAppPath) : [
|
|
211
463
|
file
|
|
212
464
|
];
|
|
213
465
|
const body = shapeFiles.flatMap((shapeFile)=>parseFile(shapeFile)?.program.body ?? []);
|
|
@@ -240,6 +492,14 @@ function check(options, topology) {
|
|
|
240
492
|
})), `${file}: must default-export ${defaultExport}`);
|
|
241
493
|
};
|
|
242
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
|
+
]);
|
|
243
503
|
const appDirectories = [
|
|
244
504
|
...new Set([
|
|
245
505
|
...directories('apps'),
|
|
@@ -300,7 +560,6 @@ function check(options, topology) {
|
|
|
300
560
|
if (violation) diagnostics.push(`${file}: ${violation}`);
|
|
301
561
|
}
|
|
302
562
|
});
|
|
303
|
-
let owner;
|
|
304
563
|
for (const appPath of appDirectories)guarded(appPath, ()=>{
|
|
305
564
|
const app = byPath.get(appPath);
|
|
306
565
|
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`);
|
|
@@ -317,19 +576,25 @@ function check(options, topology) {
|
|
|
317
576
|
}
|
|
318
577
|
return;
|
|
319
578
|
}
|
|
320
|
-
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in
|
|
579
|
+
if (app?.kind === 'vertical') assert(void 0 !== app.api, `${appPath}: vertical must declare its Effect API in topology/reference-topology.json`);
|
|
321
580
|
const stem = app?.api?.stem ?? external_node_path_default().posix.basename(appPath);
|
|
322
581
|
const rpc = app?.api?.protocol === 'rpc';
|
|
323
582
|
const contract = `${appPath}/shared/${rpc ? 'rpc' : 'api'}.ts`;
|
|
324
|
-
const client = `${appPath}/src/api/${stem}-${rpc ? 'rpc-client' : 'client'}.ts`;
|
|
325
583
|
const entry = `${appPath}/api/index.ts`;
|
|
326
584
|
for (const file of [
|
|
327
585
|
entry,
|
|
328
|
-
contract
|
|
329
|
-
client
|
|
586
|
+
contract
|
|
330
587
|
])assert(exists(file), `${file}: required API surface is missing`);
|
|
588
|
+
const packageFile = `${appPath}/package.json`;
|
|
589
|
+
assert(exists(packageFile), `${packageFile}: required API package is missing`);
|
|
590
|
+
if (!exists(packageFile)) return;
|
|
591
|
+
const manifest = JSON.parse(read(packageFile));
|
|
592
|
+
assert(manifest.exports?.['./api'] === `./shared/${rpc ? 'rpc' : 'api'}.ts`, `${packageFile}: invalid ./api export`);
|
|
593
|
+
const clientKey = `./api/${rpc ? 'rpc-client' : 'client'}`;
|
|
594
|
+
const client = clientExportFile(appPath, manifest.exports?.[clientKey]);
|
|
595
|
+
assert(void 0 !== client, `${packageFile}: invalid API client export`);
|
|
596
|
+
assert(manifest.exports?.[`./api/${rpc ? 'client' : 'rpc-client'}`] === void 0, `${packageFile}: forbidden opposite protocol client export`);
|
|
331
597
|
noPath(`${appPath}/shared/${rpc ? 'api' : 'rpc'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} contract`);
|
|
332
|
-
noPath(`${appPath}/src/api/${stem}-${rpc ? 'client' : 'rpc-client'}.ts`, `must not emit a ${rpc ? 'REST' : 'RPC'} client`);
|
|
333
598
|
if (rpc) {
|
|
334
599
|
moduleShape(entry, [
|
|
335
600
|
[
|
|
@@ -360,7 +625,7 @@ function check(options, topology) {
|
|
|
360
625
|
], 'apiRuntime');
|
|
361
626
|
moduleShape(contract, [
|
|
362
627
|
[
|
|
363
|
-
'effect/
|
|
628
|
+
'effect/rpc',
|
|
364
629
|
[
|
|
365
630
|
'Rpc',
|
|
366
631
|
'RpcGroup'
|
|
@@ -378,7 +643,7 @@ function check(options, topology) {
|
|
|
378
643
|
'Schema.Struct'
|
|
379
644
|
]);
|
|
380
645
|
} else if (exists(contract)) {
|
|
381
|
-
owner
|
|
646
|
+
const owner = options.baselinePackageDirectory ?? (0, external_microvertical_api_owner_cjs_namespaceObject.resolveBaselinePackageDirectory)(absolute(contract));
|
|
382
647
|
const apiPrefix = app?.api?.prefix ?? `/${stem}-api`;
|
|
383
648
|
const basePath = `${apiPrefix}/${stem}`;
|
|
384
649
|
const expectation = {
|
|
@@ -395,21 +660,25 @@ function check(options, topology) {
|
|
|
395
660
|
const violation = (0, external_microvertical_api_baseline_cjs_namespaceObject.microVerticalApiBaselineViolation)(stem, absolute(contract), expectation);
|
|
396
661
|
if (violation) diagnostics.push(`${contract}: ${violation}`);
|
|
397
662
|
}
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
663
|
+
if (client) {
|
|
664
|
+
const relativeContract = external_node_path_default().posix.relative(external_node_path_default().posix.dirname(client), contract);
|
|
665
|
+
moduleShape(client, [
|
|
401
666
|
[
|
|
402
|
-
|
|
403
|
-
|
|
667
|
+
effect,
|
|
668
|
+
[
|
|
669
|
+
'Effect',
|
|
670
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
671
|
+
]
|
|
672
|
+
],
|
|
673
|
+
[
|
|
674
|
+
relativeContract.startsWith('.') ? relativeContract : `./${relativeContract}`,
|
|
675
|
+
[]
|
|
404
676
|
]
|
|
405
|
-
],
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
], [
|
|
411
|
-
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
412
|
-
], [], void 0, true);
|
|
677
|
+
], [
|
|
678
|
+
rpc ? 'makeEffectRpcClient' : 'makeEffectHttpApiClient'
|
|
679
|
+
], [], void 0, appPath);
|
|
680
|
+
if (exists(contract)) diagnostics.push(...foreignClientConstructions(client, appPath, contract, stem, rpc));
|
|
681
|
+
}
|
|
413
682
|
moduleShape(`${appPath}/api/effect-api.ts`, [], [], [
|
|
414
683
|
[
|
|
415
684
|
/\bbackendFederationContract\b/u,
|
|
@@ -450,28 +719,200 @@ function check(options, topology) {
|
|
|
450
719
|
'must enable strictEffectApproach'
|
|
451
720
|
]
|
|
452
721
|
]);
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
722
|
+
});
|
|
723
|
+
const sourceRules = options.sourceRules ?? [];
|
|
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)();
|
|
726
|
+
let module;
|
|
727
|
+
try {
|
|
728
|
+
module = graph.module(absolute(file));
|
|
729
|
+
} catch (error) {
|
|
730
|
+
if (!(error instanceof external_source_analysis_cjs_namespaceObject.SourceSyntaxError)) throw error;
|
|
731
|
+
diagnostics.push(`${file}: source rules need parseable source (${error.message})`);
|
|
732
|
+
return;
|
|
733
|
+
}
|
|
734
|
+
for (const rule of sourceRules){
|
|
735
|
+
const messages = rule({
|
|
736
|
+
file,
|
|
737
|
+
module,
|
|
738
|
+
graph
|
|
739
|
+
});
|
|
740
|
+
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)`);
|
|
741
|
+
for (const message of messages)diagnostics.push(`${file}: ${message}`);
|
|
459
742
|
}
|
|
460
743
|
});
|
|
461
|
-
const
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
assert(
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
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 [];
|
|
471
759
|
}
|
|
472
|
-
|
|
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
|
+
}
|
|
473
914
|
}
|
|
474
|
-
|
|
915
|
+
for (const shell of apps.filter((app)=>'shell' === app.kind))guarded(shell.path, ()=>checkShellExports(normalize(shell.path)));
|
|
475
916
|
if (exists('topology/reference-topology.json')) guarded('topology/reference-topology.json', ()=>{
|
|
476
917
|
for (const vertical of JSON.parse(read('topology/reference-topology.json')).verticals ?? []){
|
|
477
918
|
if (vertical.api?.runtime === 'effect') {
|
|
@@ -495,11 +936,13 @@ const checkMicroVerticalApiConsumerFiles = (options)=>check(options, false);
|
|
|
495
936
|
exports.checkMicroVerticalApiBoundaries = __webpack_exports__.checkMicroVerticalApiBoundaries;
|
|
496
937
|
exports.checkMicroVerticalApiConsumerFiles = __webpack_exports__.checkMicroVerticalApiConsumerFiles;
|
|
497
938
|
exports.configuredMicroVerticalApiStem = __webpack_exports__.configuredMicroVerticalApiStem;
|
|
939
|
+
exports.createModuleGraph = __webpack_exports__.createModuleGraph;
|
|
498
940
|
exports.microVerticalApiBaselineViolation = __webpack_exports__.microVerticalApiBaselineViolation;
|
|
499
941
|
for(var __rspack_i in __webpack_exports__)if (-1 === [
|
|
500
942
|
"checkMicroVerticalApiBoundaries",
|
|
501
943
|
"checkMicroVerticalApiConsumerFiles",
|
|
502
944
|
"configuredMicroVerticalApiStem",
|
|
945
|
+
"createModuleGraph",
|
|
503
946
|
"microVerticalApiBaselineViolation"
|
|
504
947
|
].indexOf(__rspack_i)) exports[__rspack_i] = __webpack_exports__[__rspack_i];
|
|
505
948
|
Object.defineProperty(exports, '__esModule', {
|