@saptools/service-flow 0.1.44 → 0.1.45
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 +7 -0
- package/README.md +9 -1
- package/dist/{chunk-UKNPHTUS.js → chunk-BXSCE5CR.js} +1790 -1460
- package/dist/chunk-BXSCE5CR.js.map +1 -0
- package/dist/cli.js +73 -29
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +12 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/cli/doctor.ts +49 -12
- package/src/cli.ts +10 -2
- package/src/index.ts +2 -0
- package/src/linker/cross-repo-linker.ts +23 -1
- package/src/linker/odata-path-normalizer.ts +4 -1
- package/src/parsers/imported-wrapper-parser.ts +262 -0
- package/src/parsers/outbound-call-parser.ts +5 -1
- package/src/parsers/symbol-parser.ts +25 -10
- package/src/trace/evidence.ts +6 -1
- package/src/trace/implementation-hints.ts +151 -0
- package/src/trace/trace-engine.ts +52 -45
- package/src/types.ts +8 -0
- package/dist/chunk-UKNPHTUS.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -9,11 +9,12 @@ import {
|
|
|
9
9
|
parseCdsFile,
|
|
10
10
|
parseDecorators,
|
|
11
11
|
parseHandlerRegistrations,
|
|
12
|
+
parseImplementationHint,
|
|
12
13
|
parseOutboundCalls,
|
|
13
14
|
parsePackageJson,
|
|
14
15
|
parseServiceBindings,
|
|
15
16
|
trace
|
|
16
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-BXSCE5CR.js";
|
|
17
18
|
|
|
18
19
|
// src/cli.ts
|
|
19
20
|
import { Command } from "commander";
|
|
@@ -209,7 +210,7 @@ function migrate(db) {
|
|
|
209
210
|
// package.json
|
|
210
211
|
var package_default = {
|
|
211
212
|
name: "@saptools/service-flow",
|
|
212
|
-
version: "0.1.
|
|
213
|
+
version: "0.1.45",
|
|
213
214
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
214
215
|
type: "module",
|
|
215
216
|
publishConfig: {
|
|
@@ -814,6 +815,10 @@ function argumentEvidence(args, source) {
|
|
|
814
815
|
}
|
|
815
816
|
return { kind: "object_literal", properties };
|
|
816
817
|
}
|
|
818
|
+
if (ts.isArrayLiteralExpression(arg)) {
|
|
819
|
+
const elements = arg.elements.flatMap((element, index) => ts.isIdentifier(element) ? [{ index, kind: "identifier", name: element.text }] : []);
|
|
820
|
+
return { kind: "array_literal", elements };
|
|
821
|
+
}
|
|
817
822
|
return { kind: "unsupported", expression: arg.getText(source) };
|
|
818
823
|
});
|
|
819
824
|
}
|
|
@@ -860,15 +865,21 @@ function parameterPropertyAliases(fn, source) {
|
|
|
860
865
|
function parameterBindings(params) {
|
|
861
866
|
return params.flatMap((param, index) => {
|
|
862
867
|
if (ts.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
|
|
863
|
-
if (
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
868
|
+
if (ts.isObjectBindingPattern(param.name)) {
|
|
869
|
+
const properties = param.name.elements.flatMap((element) => {
|
|
870
|
+
if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
|
|
871
|
+
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
872
|
+
if (!property) return [];
|
|
873
|
+
const local = bindingLocalName(element.name, element.initializer);
|
|
874
|
+
return local ? [{ property, local }] : [];
|
|
875
|
+
});
|
|
876
|
+
return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
|
|
877
|
+
}
|
|
878
|
+
if (ts.isArrayBindingPattern(param.name)) {
|
|
879
|
+
const elements = param.name.elements.flatMap((element, elementIndex) => ts.isBindingElement(element) && !element.dotDotDotToken && ts.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
|
|
880
|
+
return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
|
|
881
|
+
}
|
|
882
|
+
return [];
|
|
872
883
|
});
|
|
873
884
|
}
|
|
874
885
|
async function parseExecutableSymbols(repoPath, filePath) {
|
|
@@ -1295,7 +1306,7 @@ async function indexWorkspace(db, workspaceId, options) {
|
|
|
1295
1306
|
function linkUpgradeWarnings(db) {
|
|
1296
1307
|
return [...schemaDriftDiagnostics(db, true), ...analyzerVersionDiagnostics(db, true)].filter((item) => ["schema_legacy_columns_present", "external_target_columns_missing_data", "reindex_required_after_upgrade", "reindex_required_after_analyzer_upgrade"].includes(String(item.code)));
|
|
1297
1308
|
}
|
|
1298
|
-
function doctorDiagnostics(db, strict) {
|
|
1309
|
+
function doctorDiagnostics(db, strict, options = {}) {
|
|
1299
1310
|
const diagnostics = db.prepare("SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id").all();
|
|
1300
1311
|
return [
|
|
1301
1312
|
...diagnostics,
|
|
@@ -1303,7 +1314,7 @@ function doctorDiagnostics(db, strict) {
|
|
|
1303
1314
|
...localServiceDiagnostics(db, strict),
|
|
1304
1315
|
...schemaDriftDiagnostics(db, strict),
|
|
1305
1316
|
...analyzerVersionDiagnostics(db, strict),
|
|
1306
|
-
...parserQualityDiagnostics(db, strict)
|
|
1317
|
+
...parserQualityDiagnostics(db, strict, options)
|
|
1307
1318
|
];
|
|
1308
1319
|
}
|
|
1309
1320
|
function healthDiagnostics(db, strict) {
|
|
@@ -1380,7 +1391,7 @@ function localServiceDiagnostics(db, strict) {
|
|
|
1380
1391
|
if (strict && implementationContext > 0) out.push({ severity: "info", code: "local_service_calls_resolved_by_implementation_context", message: `Local service calls resolved by implementation-context ownership: ${implementationContext}` });
|
|
1381
1392
|
return out;
|
|
1382
1393
|
}
|
|
1383
|
-
function parserQualityDiagnostics(db, strict) {
|
|
1394
|
+
function parserQualityDiagnostics(db, strict, options) {
|
|
1384
1395
|
if (!strict) return [];
|
|
1385
1396
|
const symbol = symbolCallQuality(db);
|
|
1386
1397
|
const dbq = dbQueryQuality(db);
|
|
@@ -1389,7 +1400,7 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1389
1400
|
identityAliasBindingQuality(db),
|
|
1390
1401
|
remoteActionNoBindingQuality(db),
|
|
1391
1402
|
contextualImplementationQuality(db),
|
|
1392
|
-
implementationCandidateQuality(db),
|
|
1403
|
+
implementationCandidateQuality(db, Boolean(options.detail)),
|
|
1393
1404
|
classInstanceNoiseQuality(db),
|
|
1394
1405
|
contextualBindingPropagationQuality(db),
|
|
1395
1406
|
nestedThisReceiverQuality(db),
|
|
@@ -1452,19 +1463,25 @@ function graphDynamicFlagQuality(db) {
|
|
|
1452
1463
|
const row = db.prepare("SELECT COUNT(*) count FROM graph_edges WHERE status='terminal' AND is_dynamic=1").get();
|
|
1453
1464
|
return { severity: Number(row.count ?? 0) > 0 ? "warning" : "info", code: "strict_graph_dynamic_flag_consistency", message: "Graph dynamic flag consistency aggregate", dynamicTerminalEdges: Number(row.count ?? 0) };
|
|
1454
1465
|
}
|
|
1455
|
-
function implementationCandidateQuality(db) {
|
|
1456
|
-
const categories = [...implementationEdgeCategories(db), missingParameterMetadataCategory(db)].filter((item) => item.count > 0);
|
|
1466
|
+
function implementationCandidateQuality(db, detail) {
|
|
1467
|
+
const categories = [...implementationEdgeCategories(db, detail), missingParameterMetadataCategory(db, detail), dynamicWrapperCategory(db, detail)].filter((item) => item.count > 0);
|
|
1457
1468
|
const total = categories.reduce((sum, item) => sum + item.count, 0);
|
|
1458
|
-
return { severity: total > 0 ? "warning" : "info", code: "strict_implementation_candidate_quality", message: "Implementation candidate ambiguity and rejection aggregate", total, categories };
|
|
1469
|
+
return { severity: total > 0 ? "warning" : "info", code: "strict_implementation_candidate_quality", message: "Implementation candidate ambiguity and rejection aggregate", total, summary: implementationSummary(categories), categories };
|
|
1459
1470
|
}
|
|
1460
|
-
function implementationEdgeCategories(db) {
|
|
1471
|
+
function implementationEdgeCategories(db, detail) {
|
|
1461
1472
|
const rows = db.prepare(`SELECT e.status,e.unresolved_reason unresolvedReason,e.evidence_json evidenceJson,o.operation_name operationName,base.operation_name baseOperation,s.service_path servicePath
|
|
1462
1473
|
FROM graph_edges e JOIN cds_operations o ON o.id=CAST(e.from_id AS INTEGER)
|
|
1463
1474
|
JOIN cds_services s ON s.id=o.service_id LEFT JOIN cds_operations base ON base.id=o.base_operation_id
|
|
1464
1475
|
WHERE e.edge_type='OPERATION_IMPLEMENTED_BY_HANDLER' AND e.status IN ('ambiguous','unresolved') ORDER BY s.service_path,o.operation_name,e.id`).all();
|
|
1465
1476
|
const grouped = /* @__PURE__ */ new Map();
|
|
1466
1477
|
for (const row of rows) addImplementationCategory(grouped, row);
|
|
1467
|
-
return [...grouped.values()].map(({ servicePaths, ...item }) => ({
|
|
1478
|
+
return [...grouped.values()].map(({ servicePaths, ...item }) => ({
|
|
1479
|
+
...item,
|
|
1480
|
+
servicePathPattern: pathPattern(servicePaths),
|
|
1481
|
+
suggestedAction: categoryAction(String(item.category)),
|
|
1482
|
+
examples: item.examples.slice(0, 3),
|
|
1483
|
+
expandedExamples: detail ? item.examples : void 0
|
|
1484
|
+
}));
|
|
1468
1485
|
}
|
|
1469
1486
|
function addImplementationCategory(grouped, row) {
|
|
1470
1487
|
const evidence = parseObject(row.evidenceJson);
|
|
@@ -1479,16 +1496,41 @@ function addImplementationCategory(grouped, row) {
|
|
|
1479
1496
|
current.examples.push({ servicePath: row.servicePath, operation: row.operationName, status: row.status, reason: row.unresolvedReason });
|
|
1480
1497
|
grouped.set(key, current);
|
|
1481
1498
|
}
|
|
1482
|
-
function missingParameterMetadataCategory(db) {
|
|
1499
|
+
function missingParameterMetadataCategory(db, detail = false) {
|
|
1483
1500
|
const examples = db.prepare(`SELECT sc.source_file sourceFile,sc.source_line sourceLine,sc.callee_expression calleeExpression
|
|
1484
1501
|
FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
|
|
1485
1502
|
WHERE sc.status='resolved' AND json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')
|
|
1486
1503
|
AND (s.evidence_json IS NULL OR json_extract(s.evidence_json,'$.parameterBindings') IS NULL)
|
|
1487
|
-
ORDER BY sc.source_file,sc.source_line
|
|
1504
|
+
ORDER BY sc.source_file,sc.source_line`).all();
|
|
1488
1505
|
const row = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
|
|
1489
1506
|
WHERE sc.status='resolved' AND json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')
|
|
1490
1507
|
AND (s.evidence_json IS NULL OR json_extract(s.evidence_json,'$.parameterBindings') IS NULL)`).get();
|
|
1491
|
-
return { category: "missing_parameter_metadata", reason: "callee symbol is missing parameter binding metadata", candidateFamily: "symbol_parameter_metadata", count: Number(row.count ?? 0), examples };
|
|
1508
|
+
return { category: "missing_parameter_metadata", reason: "callee symbol is missing parameter binding metadata", candidateFamily: "symbol_parameter_metadata", count: Number(row.count ?? 0), suggestedAction: categoryAction("missing_parameter_metadata"), examples: examples.slice(0, 3), expandedExamples: detail ? examples : void 0 };
|
|
1509
|
+
}
|
|
1510
|
+
function dynamicWrapperCategory(db, detail) {
|
|
1511
|
+
const rows = db.prepare(`SELECT source_file sourceFile,source_line sourceLine,
|
|
1512
|
+
json_extract(evidence_json,'$.receiver') receiverName,
|
|
1513
|
+
COALESCE(json_extract(evidence_json,'$.missingPathIdentifier'),json_extract(evidence_json,'$.operationPathExpression')) pathIdentifier
|
|
1514
|
+
FROM outbound_calls WHERE call_type='remote_action' AND unresolved_reason='dynamic_operation_path_identifier'
|
|
1515
|
+
ORDER BY source_file,source_line`).all();
|
|
1516
|
+
return { category: "dynamic_wrapper_paths", reason: "wrapper path cannot be proven statically", candidateFamily: "wrapper_path", count: rows.length, suggestedAction: categoryAction("dynamic_wrapper_paths"), examples: rows.slice(0, 3), expandedExamples: detail ? rows : void 0 };
|
|
1517
|
+
}
|
|
1518
|
+
function implementationSummary(categories) {
|
|
1519
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1520
|
+
for (const category of categories) {
|
|
1521
|
+
const key = [category.category, category.candidateFamily, category.reason].join("\0");
|
|
1522
|
+
const current = grouped.get(key) ?? { category: category.category, candidateFamily: category.candidateFamily, reason: category.reason, severity: "warning", suggestedAction: category.suggestedAction, count: 0 };
|
|
1523
|
+
current.count += category.count;
|
|
1524
|
+
grouped.set(key, current);
|
|
1525
|
+
}
|
|
1526
|
+
return [...grouped.values()].sort((left, right) => String(left.category).localeCompare(String(right.category)) || String(left.candidateFamily).localeCompare(String(right.candidateFamily)));
|
|
1527
|
+
}
|
|
1528
|
+
function categoryAction(category) {
|
|
1529
|
+
if (category === "duplicate_package_name_candidates") return "Use scoped --implementation-hint fields to select one repository for each ambiguous hop.";
|
|
1530
|
+
if (category === "missing_strong_ownership_evidence") return "Add an explicit package dependency, local service-path ownership, or registration ownership evidence.";
|
|
1531
|
+
if (category === "missing_parameter_metadata") return "Export a statically analyzable helper with named or destructured parameters.";
|
|
1532
|
+
if (category === "dynamic_wrapper_paths") return "Pass a literal path or provide the reported runtime identifier with --var key=value.";
|
|
1533
|
+
return "Inspect the capped examples and add stronger implementation ownership evidence.";
|
|
1492
1534
|
}
|
|
1493
1535
|
function implementationCategory(row, evidence) {
|
|
1494
1536
|
const reasons = JSON.stringify([evidence.ambiguityReasons, evidence.candidateFamilies, evidence.candidates, row.unresolvedReason]);
|
|
@@ -1788,7 +1830,7 @@ function createProgram() {
|
|
|
1788
1830
|
);
|
|
1789
1831
|
}).catch(fail)
|
|
1790
1832
|
);
|
|
1791
|
-
program.command("trace").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--handler <name>").option("--depth <n>", "trace depth", "25").option("--format <format>", "table|json|mermaid", "table").option("--include-external").option("--include-db").option("--include-async").option("--implementation-repo <name>").option("--var <key=value>", "dynamic variable", collect, []).action(
|
|
1833
|
+
program.command("trace").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--handler <name>").option("--depth <n>", "trace depth", "25").option("--format <format>", "table|json|mermaid", "table").option("--include-external").option("--include-db").option("--include-async").option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).action(
|
|
1792
1834
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
|
|
1793
1835
|
const result = trace(
|
|
1794
1836
|
db,
|
|
@@ -1805,7 +1847,8 @@ function createProgram() {
|
|
|
1805
1847
|
includeExternal: Boolean(opts.includeExternal),
|
|
1806
1848
|
includeDb: Boolean(opts.includeDb),
|
|
1807
1849
|
includeAsync: Boolean(opts.includeAsync),
|
|
1808
|
-
implementationRepo: opts.implementationRepo
|
|
1850
|
+
implementationRepo: opts.implementationRepo,
|
|
1851
|
+
implementationHints: opts.implementationHint.map(parseImplementationHint)
|
|
1809
1852
|
}
|
|
1810
1853
|
);
|
|
1811
1854
|
process.stdout.write(
|
|
@@ -1874,7 +1917,7 @@ function createProgram() {
|
|
|
1874
1917
|
process.stdout.write(renderJson(rows));
|
|
1875
1918
|
}).catch(fail)
|
|
1876
1919
|
);
|
|
1877
|
-
program.command("graph").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--format <format>", "mermaid|json", "mermaid").option("--implementation-repo <name>").option("--var <key=value>", "dynamic variable", collect, []).action(
|
|
1920
|
+
program.command("graph").option("--workspace <path>").option("--repo <name>").option("--operation <name>").option("--service <path>").option("--path <operationPath>").option("--format <format>", "mermaid|json", "mermaid").option("--implementation-repo <name>").option("--implementation-hint <scope>", "scoped implementation hint", collect, []).option("--var <key=value>", "dynamic variable", collect, []).action(
|
|
1878
1921
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
|
|
1879
1922
|
const result = trace(
|
|
1880
1923
|
db,
|
|
@@ -1890,7 +1933,8 @@ function createProgram() {
|
|
|
1890
1933
|
includeDb: true,
|
|
1891
1934
|
includeExternal: true,
|
|
1892
1935
|
vars: parseVars(opts.var),
|
|
1893
|
-
implementationRepo: opts.implementationRepo
|
|
1936
|
+
implementationRepo: opts.implementationRepo,
|
|
1937
|
+
implementationHints: opts.implementationHint.map(parseImplementationHint)
|
|
1894
1938
|
}
|
|
1895
1939
|
);
|
|
1896
1940
|
process.stdout.write(
|
|
@@ -1915,9 +1959,9 @@ function createProgram() {
|
|
|
1915
1959
|
process.stdout.write(renderJson(rows));
|
|
1916
1960
|
}).catch(fail)
|
|
1917
1961
|
);
|
|
1918
|
-
program.command("doctor").option("--workspace <path>").option("--strict").action(
|
|
1962
|
+
program.command("doctor").option("--workspace <path>").option("--strict").option("--detail").action(
|
|
1919
1963
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
|
|
1920
|
-
const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict));
|
|
1964
|
+
const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict), { detail: Boolean(opts.detail) });
|
|
1921
1965
|
process.stdout.write(
|
|
1922
1966
|
allDiagnostics.length ? renderJson(allDiagnostics) : `${pc.green("No diagnostics recorded")}
|
|
1923
1967
|
`
|