@saptools/service-flow 0.1.26 → 0.1.28
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/CHANGELOG.md +14 -0
- package/dist/{chunk-Q3J2QDNX.js → chunk-SWPGRM7Z.js} +49 -16
- package/dist/chunk-SWPGRM7Z.js.map +1 -0
- package/dist/cli.js +73 -8
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-Q3J2QDNX.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -12,7 +12,7 @@ import {
|
|
|
12
12
|
parsePackageJson,
|
|
13
13
|
parseServiceBindings,
|
|
14
14
|
trace
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-SWPGRM7Z.js";
|
|
16
16
|
|
|
17
17
|
// src/cli.ts
|
|
18
18
|
import { Command } from "commander";
|
|
@@ -188,7 +188,7 @@ function migrate(db) {
|
|
|
188
188
|
// package.json
|
|
189
189
|
var package_default = {
|
|
190
190
|
name: "@saptools/service-flow",
|
|
191
|
-
version: "0.1.
|
|
191
|
+
version: "0.1.28",
|
|
192
192
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
193
193
|
type: "module",
|
|
194
194
|
publishConfig: {
|
|
@@ -789,6 +789,41 @@ function bindingLocalName(name, initializer) {
|
|
|
789
789
|
if (initializer && ts.isIdentifier(initializer)) return initializer.text;
|
|
790
790
|
return void 0;
|
|
791
791
|
}
|
|
792
|
+
function objectPatternAliases(pattern, parameter, source, lineNode) {
|
|
793
|
+
return pattern.elements.flatMap((element) => {
|
|
794
|
+
if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
|
|
795
|
+
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
796
|
+
if (!property) return [];
|
|
797
|
+
const local = bindingLocalName(element.name, element.initializer);
|
|
798
|
+
return local ? [{ parameter, property, local, kind: "object_parameter_destructure", line: lineOf(source, lineNode.getStart(source)) }] : [];
|
|
799
|
+
});
|
|
800
|
+
}
|
|
801
|
+
function parameterPropertyAliases(fn, source) {
|
|
802
|
+
const parameterNames = new Set(fn.parameters.flatMap((param) => ts.isIdentifier(param.name) ? [param.name.text] : []));
|
|
803
|
+
if (!fn.body || parameterNames.size === 0) return [];
|
|
804
|
+
const aliases = [];
|
|
805
|
+
const addFromAssignment = (left, right, node) => {
|
|
806
|
+
if (!ts.isObjectLiteralExpression(left) || !ts.isIdentifier(right) || !parameterNames.has(right.text)) return;
|
|
807
|
+
for (const prop of left.properties) {
|
|
808
|
+
if (!ts.isPropertyAssignment(prop)) continue;
|
|
809
|
+
const property = nameOf(prop.name);
|
|
810
|
+
if (property && ts.isIdentifier(prop.initializer)) aliases.push({ parameter: right.text, property, local: prop.initializer.text, kind: "object_parameter_destructure", line: lineOf(source, node.getStart(source)) });
|
|
811
|
+
}
|
|
812
|
+
};
|
|
813
|
+
const visit = (node) => {
|
|
814
|
+
if (ts.isVariableDeclaration(node) && ts.isObjectBindingPattern(node.name) && node.initializer && ts.isIdentifier(node.initializer) && parameterNames.has(node.initializer.text)) aliases.push(...objectPatternAliases(node.name, node.initializer.text, source, node));
|
|
815
|
+
if (ts.isBinaryExpression(node) && node.operatorToken.kind === ts.SyntaxKind.EqualsToken) addFromAssignment(ts.isParenthesizedExpression(node.left) ? node.left.expression : node.left, node.right, node);
|
|
816
|
+
ts.forEachChild(node, visit);
|
|
817
|
+
};
|
|
818
|
+
visit(fn.body);
|
|
819
|
+
const seen = /* @__PURE__ */ new Set();
|
|
820
|
+
return aliases.filter((alias) => {
|
|
821
|
+
const key = `${alias.parameter}.${alias.property}:${alias.local}`;
|
|
822
|
+
if (seen.has(key)) return false;
|
|
823
|
+
seen.add(key);
|
|
824
|
+
return true;
|
|
825
|
+
});
|
|
826
|
+
}
|
|
792
827
|
function parameterBindings(params) {
|
|
793
828
|
return params.flatMap((param, index) => {
|
|
794
829
|
if (ts.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
|
|
@@ -826,7 +861,8 @@ async function parseExecutableSymbols(repoPath, filePath) {
|
|
|
826
861
|
const bindings = isFunctionLike(node) ? parameterBindings(node.parameters) : void 0;
|
|
827
862
|
const params = bindings?.flatMap((binding) => binding.kind === "identifier" ? [binding.name] : []);
|
|
828
863
|
const sourceEvidence = evidence ?? (classMemberExported ? { source: "exported_class_member", exportedClass: parentRoot, memberKind: (ts.getCombinedModifierFlags(node) & ts.ModifierFlags.Static) !== 0 ? "static_method" : "class_method", parameters: params } : declaredExportName ? { exportedName: declaredExportName, source: "export_declaration" } : objectExported ? { exportedName: qualifiedName, source: "exported_object_literal" } : void 0);
|
|
829
|
-
const
|
|
864
|
+
const aliases = isFunctionLike(node) ? parameterPropertyAliases(node, source) : [];
|
|
865
|
+
const parameterEvidence = { ...bindings && bindings.length > 0 ? { parameters: params, parameterBindings: bindings } : {}, ...aliases.length > 0 ? { parameterPropertyAliases: aliases } : {} };
|
|
830
866
|
symbols.push({ kind, localName: kind === "object_method" ? qualifiedName : localName, exportedName: effectiveExportedName, qualifiedName, sourceFile, startLine: lineOf(source, node.getStart(source)), endLine: lineOf(source, node.getEnd()), startOffset: node.getStart(source), endOffset: node.getEnd(), exported: exported(node) || Boolean(effectiveExportedName), importExportEvidence: sourceEvidence ? { ...sourceEvidence, ...parameterEvidence } : bindings && bindings.length > 0 ? parameterEvidence : void 0 });
|
|
831
867
|
};
|
|
832
868
|
const addAliasSymbol = (objectName, propertyName, node, targetImportSource) => {
|
|
@@ -1369,13 +1405,42 @@ function contextualBindingPropagationQuality(db) {
|
|
|
1369
1405
|
WHERE json_extract(sc.evidence_json,'$.callArguments[0].kind')='object_literal'
|
|
1370
1406
|
AND json_extract(s.evidence_json,'$.parameterBindings[0].kind')='object_pattern'
|
|
1371
1407
|
AND json_array_length(json_extract(sc.evidence_json,'$.callArguments[0].properties')) > json_array_length(json_extract(s.evidence_json,'$.parameterBindings[0].properties'))`).get();
|
|
1372
|
-
const opportunities = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath,'
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1408
|
+
const opportunities = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,json_extract(c.evidence_json,'$.receiver') receiverName,c.operation_path_expr operationPath,b.alias bindingAlias,b.alias_expr bindingAliasExpr,b.service_path_expr servicePathExpr,b.destination_expr destinationExpr,req.service_path requireServicePath,req.destination requireDestination,COALESCE(e.status,'missing_edge') persistedStatus,
|
|
1409
|
+
CASE
|
|
1410
|
+
WHEN (b.alias_expr LIKE '%$%' OR b.service_path_expr LIKE '%$%' OR b.destination_expr LIKE '%$%') THEN 'runtime_variables_required'
|
|
1411
|
+
WHEN b.alias IS NOT NULL AND req.id IS NULL AND b.service_path_expr IS NULL THEN 'alias_without_matching_cds_requires'
|
|
1412
|
+
WHEN req.id IS NOT NULL AND COALESCE(e.status,'missing_edge')!='resolved' THEN 'cds_requires_present_but_persisted_resolution_unresolved'
|
|
1413
|
+
ELSE 'trace_time_contextual_binding_candidate'
|
|
1414
|
+
END contextualStatus
|
|
1415
|
+
FROM outbound_calls c
|
|
1416
|
+
LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1417
|
+
LEFT JOIN service_bindings b ON b.id=c.service_binding_id
|
|
1418
|
+
LEFT JOIN cds_requires req ON req.repo_id=c.repo_id AND req.alias=b.alias
|
|
1419
|
+
WHERE c.call_type='remote_action' AND json_extract(c.evidence_json,'$.receiver') IS NOT NULL
|
|
1420
|
+
AND (c.service_binding_id IS NULL OR e.status IS NULL OR e.status!='resolved')
|
|
1376
1421
|
AND EXISTS (SELECT 1 FROM symbol_calls sc WHERE sc.status='resolved' AND sc.source_file=c.source_file)
|
|
1377
1422
|
ORDER BY c.source_file,c.source_line LIMIT 8`).all();
|
|
1378
|
-
|
|
1423
|
+
const statusRows = db.prepare(`SELECT contextualStatus,COUNT(*) count FROM (
|
|
1424
|
+
SELECT CASE
|
|
1425
|
+
WHEN (b.alias_expr LIKE '%$%' OR b.service_path_expr LIKE '%$%' OR b.destination_expr LIKE '%$%') THEN 'runtime_variables_required'
|
|
1426
|
+
WHEN b.alias IS NOT NULL AND req.id IS NULL AND b.service_path_expr IS NULL THEN 'alias_without_matching_cds_requires'
|
|
1427
|
+
WHEN req.id IS NOT NULL AND COALESCE(e.status,'missing_edge')!='resolved' THEN 'cds_requires_present_but_persisted_resolution_unresolved'
|
|
1428
|
+
ELSE 'trace_time_contextual_binding_candidate'
|
|
1429
|
+
END contextualStatus
|
|
1430
|
+
FROM outbound_calls c
|
|
1431
|
+
LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1432
|
+
LEFT JOIN service_bindings b ON b.id=c.service_binding_id
|
|
1433
|
+
LEFT JOIN cds_requires req ON req.repo_id=c.repo_id AND req.alias=b.alias
|
|
1434
|
+
WHERE c.call_type='remote_action' AND json_extract(c.evidence_json,'$.receiver') IS NOT NULL
|
|
1435
|
+
AND (c.service_binding_id IS NULL OR e.status IS NULL OR e.status!='resolved')
|
|
1436
|
+
AND EXISTS (SELECT 1 FROM symbol_calls sc WHERE sc.status='resolved' AND sc.source_file=c.source_file)
|
|
1437
|
+
) GROUP BY contextualStatus ORDER BY count DESC,contextualStatus`).all();
|
|
1438
|
+
const resolvedContextual = db.prepare(`SELECT COUNT(*) count FROM outbound_calls c JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT) WHERE c.call_type='remote_action' AND e.status='resolved' AND c.service_binding_id IS NOT NULL`).get();
|
|
1439
|
+
const totalOpportunities = statusRows.reduce((sum, row) => sum + Number(row.count ?? 0), 0);
|
|
1440
|
+
const actionableStatuses = /* @__PURE__ */ new Set(["alias_without_matching_cds_requires", "cds_requires_present_but_persisted_resolution_unresolved", "trace_time_contextual_binding_candidate"]);
|
|
1441
|
+
const actionableOpportunityCount = statusRows.reduce((sum, row) => actionableStatuses.has(String(row.contextualStatus)) ? sum + Number(row.count ?? 0) : sum, 0);
|
|
1442
|
+
const severity = Number(missingMetadata.count ?? 0) + Number(destructuredUnmapped.count ?? 0) + actionableOpportunityCount > 0 ? "warning" : "info";
|
|
1443
|
+
return { severity, code: "strict_contextual_binding_propagation_quality", message: "Contextual service-client propagation opportunities for trace-time helper resolution", localSymbolCallsWithServiceClientArguments: Number(serviceClientCalls.count ?? 0), calleeSymbolsMissingParameterMetadata: Number(missingMetadata.count ?? 0), destructuredObjectParametersPossiblyUnmapped: Number(destructuredUnmapped.count ?? 0), contextualHelperSendsResolvedDuringPersistedLink: Number(resolvedContextual.count ?? 0), traceTimeContextualOpportunities: totalOpportunities, traceTimeContextualOpportunityBreakdown: statusRows.length > 0 ? statusRows : [{ contextualStatus: "no_contextual_opportunity", count: 0 }], exampleCount: opportunities.length, examples: opportunities };
|
|
1379
1444
|
}
|
|
1380
1445
|
function nestedThisReceiverQuality(db) {
|
|
1381
1446
|
const aggregate = db.prepare(`SELECT COUNT(*) total,
|