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