@saptools/service-flow 0.1.18 → 0.1.20
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/README.md +3 -11
- package/TECHNICAL-NOTE.md +13 -0
- package/dist/{chunk-DUGM7FRA.js → chunk-EKLZJJW7.js} +209 -24
- package/dist/chunk-EKLZJJW7.js.map +1 -0
- package/dist/cli.js +63 -2
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-DUGM7FRA.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -3,6 +3,7 @@ import {
|
|
|
3
3
|
containsSupportedOutboundCall,
|
|
4
4
|
discoverRepositories,
|
|
5
5
|
linkWorkspace,
|
|
6
|
+
normalizeODataOperationInvocationPath,
|
|
6
7
|
normalizePath,
|
|
7
8
|
parseCdsFile,
|
|
8
9
|
parseDecorators,
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
parsePackageJson,
|
|
12
13
|
parseServiceBindings,
|
|
13
14
|
trace
|
|
14
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-EKLZJJW7.js";
|
|
15
16
|
|
|
16
17
|
// src/cli.ts
|
|
17
18
|
import { Command } from "commander";
|
|
@@ -187,7 +188,7 @@ function migrate(db) {
|
|
|
187
188
|
// package.json
|
|
188
189
|
var package_default = {
|
|
189
190
|
name: "@saptools/service-flow",
|
|
190
|
-
version: "0.1.
|
|
191
|
+
version: "0.1.20",
|
|
191
192
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
192
193
|
type: "module",
|
|
193
194
|
publishConfig: {
|
|
@@ -1176,7 +1177,13 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1176
1177
|
const outboundTotal = Number(outbound.total ?? 0);
|
|
1177
1178
|
const outboundWithoutOwnership = Number(outbound.withoutOwnership ?? 0);
|
|
1178
1179
|
const outboundWithoutOwnershipRatio = outboundTotal === 0 ? 0 : Number((outboundWithoutOwnership / outboundTotal).toFixed(4));
|
|
1180
|
+
const remoteQuery = remoteQueryTargetQuality(db);
|
|
1181
|
+
const invocation = odataInvocationResolutionQuality(db);
|
|
1182
|
+
const remoteAction = remoteActionTargetQuality(db);
|
|
1179
1183
|
return [
|
|
1184
|
+
remoteQuery,
|
|
1185
|
+
invocation,
|
|
1186
|
+
remoteAction,
|
|
1180
1187
|
{ severity: Number(evidence.nonObject ?? 0) > 0 ? "warning" : "info", code: "strict_symbol_call_evidence_quality", message: "Symbol-call evidence JSON object aggregate", total: Number(evidence.total ?? 0), nonObject: Number(evidence.nonObject ?? 0) },
|
|
1181
1188
|
{ severity: Number(outboundEvidence.missing ?? 0) + Number(outboundEvidence.invalid ?? 0) + Number(outboundEvidence.nonObject ?? 0) > 0 ? "warning" : "info", code: "strict_outbound_evidence_quality", message: "Outbound parser evidence JSON object aggregate", total: Number(outboundEvidence.total ?? 0), missing: Number(outboundEvidence.missing ?? 0), invalid: Number(outboundEvidence.invalid ?? 0), nonObject: Number(outboundEvidence.nonObject ?? 0), examples: outboundEvidenceExamples },
|
|
1182
1189
|
{ severity: Number(graphEvidence.nonObject ?? 0) > 0 || Number(graphEvidence.withOutboundEvidence ?? 0) < Number(graphEvidence.total ?? 0) ? "warning" : "info", code: "strict_graph_evidence_quality", message: "Call-derived graph evidence and parser-evidence propagation aggregate", total: Number(graphEvidence.total ?? 0), nonObject: Number(graphEvidence.nonObject ?? 0), withOutboundEvidence: Number(graphEvidence.withOutboundEvidence ?? 0), examples: graphEvidenceExamples },
|
|
@@ -1187,6 +1194,60 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1187
1194
|
{ severity: outboundWithoutOwnershipRatio > outboundUnownedThreshold ? "warning" : "info", code: "strict_outbound_source_ownership_quality", message: "Outbound call source-symbol ownership aggregate", total: outboundTotal, withoutOwnership: outboundWithoutOwnership, withoutOwnershipRatio: outboundWithoutOwnershipRatio, withoutOwnershipRatioThreshold: outboundUnownedThreshold, ownerlessByType, ownerlessByCategory, ownerlessExamples }
|
|
1188
1195
|
];
|
|
1189
1196
|
}
|
|
1197
|
+
function remoteQueryTargetQuality(db) {
|
|
1198
|
+
const aggregate = db.prepare(`SELECT COUNT(*) total,
|
|
1199
|
+
SUM(CASE WHEN e.edge_type='HANDLER_RUNS_REMOTE_QUERY' AND e.status='terminal' THEN 1 ELSE 0 END) terminal,
|
|
1200
|
+
SUM(CASE WHEN e.edge_type='HANDLER_RUNS_REMOTE_QUERY' AND e.to_id GLOB '[0-9]*' THEN 1 ELSE 0 END) numericTargets,
|
|
1201
|
+
SUM(CASE WHEN e.edge_type='UNRESOLVED_EDGE' OR e.status='unresolved' THEN 1 ELSE 0 END) unresolved
|
|
1202
|
+
FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT) WHERE c.call_type='remote_query'`).get();
|
|
1203
|
+
const examples = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,c.query_entity queryEntity,e.edge_type edgeType,e.status status,e.to_id target
|
|
1204
|
+
FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1205
|
+
WHERE c.call_type='remote_query' AND (e.id IS NULL OR e.edge_type<>'HANDLER_RUNS_REMOTE_QUERY' OR e.status<>'terminal' OR e.to_id GLOB '[0-9]*')
|
|
1206
|
+
ORDER BY c.source_file,c.source_line LIMIT 5`).all();
|
|
1207
|
+
const numericTargets = Number(aggregate.numericTargets ?? 0);
|
|
1208
|
+
const unresolved = Number(aggregate.unresolved ?? 0);
|
|
1209
|
+
return { severity: numericTargets + unresolved > 0 ? "warning" : "info", code: "strict_remote_query_target_quality", message: "Remote query terminal target quality aggregate", totalRemoteQueryCalls: Number(aggregate.total ?? 0), terminalRemoteQueryEdges: Number(aggregate.terminal ?? 0), numericTargetCount: numericTargets, unresolvedRemoteQueryCount: unresolved, examples };
|
|
1210
|
+
}
|
|
1211
|
+
function remoteActionTargetQuality(db) {
|
|
1212
|
+
const aggregate = db.prepare(`SELECT COUNT(*) total,
|
|
1213
|
+
SUM(CASE WHEN e.status='unresolved' THEN 1 ELSE 0 END) unresolved,
|
|
1214
|
+
SUM(CASE WHEN e.status='unresolved' AND e.to_id GLOB '[0-9]*' THEN 1 ELSE 0 END) numericTargets,
|
|
1215
|
+
SUM(CASE WHEN e.status='unresolved' AND (e.to_id='Remote action: unknown path' OR e.to_id='Remote action: dynamic path') THEN 1 ELSE 0 END) semanticTargets
|
|
1216
|
+
FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT) WHERE c.call_type='remote_action'`).get();
|
|
1217
|
+
const examples = db.prepare(`SELECT c.source_file sourceFile,c.source_line sourceLine,c.operation_path_expr operationPath,e.status status,e.to_id target
|
|
1218
|
+
FROM outbound_calls c LEFT JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1219
|
+
WHERE c.call_type='remote_action' AND e.status='unresolved' AND e.to_id GLOB '[0-9]*' ORDER BY c.source_file,c.source_line LIMIT 5`).all();
|
|
1220
|
+
const numericTargets = Number(aggregate.numericTargets ?? 0);
|
|
1221
|
+
return { severity: numericTargets > 0 ? "warning" : "info", code: "strict_remote_action_target_quality", message: "Remote action unresolved target quality aggregate", totalRemoteActionCalls: Number(aggregate.total ?? 0), unresolvedRemoteActionCalls: Number(aggregate.unresolved ?? 0), numericUnresolvedTargetCount: numericTargets, semanticUnknownOrDynamicTargetCount: Number(aggregate.semanticTargets ?? 0), examples };
|
|
1222
|
+
}
|
|
1223
|
+
function odataInvocationResolutionQuality(db) {
|
|
1224
|
+
const aggregate = db.prepare(`SELECT COUNT(*) total,
|
|
1225
|
+
SUM(CASE WHEN e.status='resolved' THEN 1 ELSE 0 END) resolved,
|
|
1226
|
+
SUM(CASE WHEN e.status='dynamic' THEN 1 ELSE 0 END) dynamic,
|
|
1227
|
+
SUM(CASE WHEN e.status='ambiguous' THEN 1 ELSE 0 END) ambiguous,
|
|
1228
|
+
SUM(CASE WHEN e.status='unresolved' THEN 1 ELSE 0 END) unresolved
|
|
1229
|
+
FROM outbound_calls c JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1230
|
+
WHERE c.call_type='remote_action' AND c.operation_path_expr LIKE '%(%'`).get();
|
|
1231
|
+
const rows = db.prepare(`SELECT c.id id,c.operation_path_expr operationPathExpr,c.source_file sourceFile,c.source_line sourceLine,e.status status,e.unresolved_reason unresolvedReason
|
|
1232
|
+
FROM outbound_calls c JOIN graph_edges e ON e.from_kind='call' AND e.from_id=CAST(c.id AS TEXT)
|
|
1233
|
+
WHERE c.call_type='remote_action' AND e.status IN ('unresolved','ambiguous') AND c.operation_path_expr LIKE '%(%'
|
|
1234
|
+
ORDER BY c.source_file,c.source_line LIMIT 100`).all();
|
|
1235
|
+
const examples = [];
|
|
1236
|
+
let unresolvedMatchingIndexedOperation = 0;
|
|
1237
|
+
let ambiguousNormalizedCalls = 0;
|
|
1238
|
+
for (const row of rows) {
|
|
1239
|
+
const normalized = normalizeODataOperationInvocationPath(row.operationPathExpr);
|
|
1240
|
+
if (!normalized?.wasInvocation) continue;
|
|
1241
|
+
const normalizedName = normalized.normalizedOperationPath.replace(/^\//, "");
|
|
1242
|
+
const simpleName = normalizedName.split(".").at(-1) ?? normalizedName;
|
|
1243
|
+
const candidates = db.prepare("SELECT s.service_path servicePath,o.operation_path operationPath,o.operation_name operationName FROM cds_operations o JOIN cds_services s ON s.id=o.service_id WHERE o.operation_path IN (?,?) OR o.operation_name IN (?,?) ORDER BY s.service_path,o.operation_name LIMIT 5").all(normalized.normalizedOperationPath, `/${simpleName}`, normalizedName, simpleName);
|
|
1244
|
+
if (candidates.length === 0) continue;
|
|
1245
|
+
if (row.status === "ambiguous") ambiguousNormalizedCalls += 1;
|
|
1246
|
+
if (row.status === "unresolved") unresolvedMatchingIndexedOperation += 1;
|
|
1247
|
+
if (examples.length < 5) examples.push({ sourceFile: row.sourceFile, sourceLine: row.sourceLine, rawOperationPath: row.operationPathExpr, normalizedOperationPath: normalized.normalizedOperationPath, candidateCount: candidates.length, candidates });
|
|
1248
|
+
}
|
|
1249
|
+
return { severity: unresolvedMatchingIndexedOperation + ambiguousNormalizedCalls > 0 ? "warning" : "info", code: "strict_odata_invocation_resolution_quality", message: "OData invocation-path resolution quality aggregate", totalInvocationRemoteActions: Number(aggregate.total ?? 0), resolvedInvocationCalls: Number(aggregate.resolved ?? 0), dynamicInvocationCalls: Number(aggregate.dynamic ?? 0), ambiguousInvocationCalls: Number(aggregate.ambiguous ?? 0), unresolvedInvocationCalls: Number(aggregate.unresolved ?? 0), ambiguousNormalizedCalls, unresolvedNormalizedCallsWithIndexedCandidates: unresolvedMatchingIndexedOperation, examples };
|
|
1250
|
+
}
|
|
1190
1251
|
function createProgram() {
|
|
1191
1252
|
const program = new Command();
|
|
1192
1253
|
program.name("service-flow").description(
|