@saptools/service-flow 0.1.53 → 0.1.55
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 +11 -0
- package/README.md +12 -5
- package/TECHNICAL-NOTE.md +13 -0
- package/dist/{chunk-LFH7C46B.js → chunk-GXYVIHJ5.js} +635 -135
- package/dist/chunk-GXYVIHJ5.js.map +1 -0
- package/dist/cli.js +71 -26
- package/dist/cli.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/src/cli/001-doctor-projection.ts +3 -7
- package/src/cli.ts +24 -16
- package/src/linker/000-implementation-candidates.ts +35 -2
- package/src/linker/001-implementation-evidence-projection.ts +78 -6
- package/src/output/000-stdout-policy.ts +65 -0
- package/src/output/table-output.ts +11 -2
- package/src/parsers/outbound-call-parser.ts +116 -11
- package/src/trace/008-contextual-runtime-state.ts +294 -0
- package/src/trace/009-selected-handler-provenance.ts +186 -0
- package/src/trace/evidence.ts +103 -31
- package/src/trace/trace-engine.ts +67 -82
- package/src/utils/000-bounded-projection.ts +20 -15
- package/dist/chunk-LFH7C46B.js.map +0 -1
package/dist/cli.js
CHANGED
|
@@ -29,14 +29,13 @@ import {
|
|
|
29
29
|
parsePackageJson,
|
|
30
30
|
parseServiceBindings,
|
|
31
31
|
parseVars,
|
|
32
|
-
|
|
32
|
+
projectBoundedInOrder,
|
|
33
33
|
reposByName,
|
|
34
34
|
selectorRepoAmbiguousDiagnostic,
|
|
35
|
-
stableProjectionValue,
|
|
36
35
|
trace,
|
|
37
36
|
upsertRepository,
|
|
38
37
|
upsertWorkspace
|
|
39
|
-
} from "./chunk-
|
|
38
|
+
} from "./chunk-GXYVIHJ5.js";
|
|
40
39
|
|
|
41
40
|
// src/cli.ts
|
|
42
41
|
import { Command } from "commander";
|
|
@@ -238,7 +237,7 @@ function migrate(db) {
|
|
|
238
237
|
// package.json
|
|
239
238
|
var package_default = {
|
|
240
239
|
name: "@saptools/service-flow",
|
|
241
|
-
version: "0.1.
|
|
240
|
+
version: "0.1.55",
|
|
242
241
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
243
242
|
type: "module",
|
|
244
243
|
publishConfig: {
|
|
@@ -1178,7 +1177,7 @@ function boundDoctorValue(value) {
|
|
|
1178
1177
|
output[key] = boundDoctorValue(child);
|
|
1179
1178
|
continue;
|
|
1180
1179
|
}
|
|
1181
|
-
const projection =
|
|
1180
|
+
const projection = projectBoundedInOrder(child.map(boundDoctorValue));
|
|
1182
1181
|
output[key] = projection.items;
|
|
1183
1182
|
addProjectionMetadata(output, input, key, projection);
|
|
1184
1183
|
}
|
|
@@ -1240,9 +1239,6 @@ function projectionStem(key) {
|
|
|
1240
1239
|
};
|
|
1241
1240
|
return stems[key] ?? "repository";
|
|
1242
1241
|
}
|
|
1243
|
-
function compareDiagnostic(left, right) {
|
|
1244
|
-
return stableProjectionValue(left).localeCompare(stableProjectionValue(right));
|
|
1245
|
-
}
|
|
1246
1242
|
function numericValue(value) {
|
|
1247
1243
|
return typeof value === "number" && Number.isFinite(value) ? value : 0;
|
|
1248
1244
|
}
|
|
@@ -1851,13 +1847,20 @@ function asRecords(value) {
|
|
|
1851
1847
|
|
|
1852
1848
|
// src/output/table-output.ts
|
|
1853
1849
|
function location(evidence) {
|
|
1850
|
+
const selected = isRecord(evidence.selectedHandler) ? evidence.selectedHandler : void 0;
|
|
1851
|
+
const selectedFile = selected?.sourceFile;
|
|
1852
|
+
const selectedLine = selected?.sourceLine;
|
|
1853
|
+
if (selectedFile || selectedLine)
|
|
1854
|
+
return `${String(selectedFile ?? "")}:${String(selectedLine ?? "")}`;
|
|
1854
1855
|
const file = evidence.file ?? evidence.sourceFile ?? evidence.handlerSourceFile ?? evidence.operationSourceFile ?? evidence.registrationSourceFile;
|
|
1855
1856
|
const line = evidence.line ?? evidence.sourceLine ?? evidence.handlerSourceLine ?? evidence.operationSourceLine ?? evidence.registrationSourceLine;
|
|
1856
1857
|
if (file || line) return `${String(file ?? "")}:${String(line ?? "")}`;
|
|
1857
1858
|
const candidates = evidence.candidates;
|
|
1859
|
+
if (Array.isArray(candidates) && candidates.some((candidate) => isRecord(candidate) && candidate.methodId !== void 0)) return ":";
|
|
1858
1860
|
if (Array.isArray(candidates) && candidates.length > 0) {
|
|
1859
|
-
const first = candidates
|
|
1860
|
-
|
|
1861
|
+
const first = candidates.find(isRecord);
|
|
1862
|
+
if (first)
|
|
1863
|
+
return `${String(first.sourceFile ?? "")}:${String(first.sourceLine ?? "")}`;
|
|
1861
1864
|
}
|
|
1862
1865
|
return ":";
|
|
1863
1866
|
}
|
|
@@ -2056,6 +2059,44 @@ function renderMermaid(trace2) {
|
|
|
2056
2059
|
`;
|
|
2057
2060
|
}
|
|
2058
2061
|
|
|
2062
|
+
// src/output/000-stdout-policy.ts
|
|
2063
|
+
var writers = /* @__PURE__ */ new WeakMap();
|
|
2064
|
+
function isRecord3(value) {
|
|
2065
|
+
return Boolean(value && typeof value === "object" && !Array.isArray(value));
|
|
2066
|
+
}
|
|
2067
|
+
function isBrokenPipe(error) {
|
|
2068
|
+
return isRecord3(error) && error.code === "EPIPE";
|
|
2069
|
+
}
|
|
2070
|
+
function asError(value) {
|
|
2071
|
+
return value instanceof Error ? value : new Error(String(value));
|
|
2072
|
+
}
|
|
2073
|
+
function handleStreamError(state, error, onUnexpectedError) {
|
|
2074
|
+
state.blocked = true;
|
|
2075
|
+
if (isBrokenPipe(error) || state.unexpectedReported) return;
|
|
2076
|
+
state.unexpectedReported = true;
|
|
2077
|
+
onUnexpectedError(error);
|
|
2078
|
+
}
|
|
2079
|
+
function createStdoutWriter(stream, onUnexpectedError) {
|
|
2080
|
+
const existing = writers.get(stream);
|
|
2081
|
+
if (existing) return existing.writer;
|
|
2082
|
+
const state = { blocked: false, unexpectedReported: false };
|
|
2083
|
+
const writer = {
|
|
2084
|
+
write(value) {
|
|
2085
|
+
if (state.blocked) return false;
|
|
2086
|
+
try {
|
|
2087
|
+
stream.write(value);
|
|
2088
|
+
return true;
|
|
2089
|
+
} catch (error) {
|
|
2090
|
+
handleStreamError(state, asError(error), onUnexpectedError);
|
|
2091
|
+
return false;
|
|
2092
|
+
}
|
|
2093
|
+
}
|
|
2094
|
+
};
|
|
2095
|
+
stream.on("error", (error) => handleStreamError(state, error, onUnexpectedError));
|
|
2096
|
+
writers.set(stream, { state, writer });
|
|
2097
|
+
return writer;
|
|
2098
|
+
}
|
|
2099
|
+
|
|
2059
2100
|
// src/cli/000-clean.ts
|
|
2060
2101
|
import fs6 from "fs/promises";
|
|
2061
2102
|
import path6 from "path";
|
|
@@ -2116,6 +2157,10 @@ async function markCleanClaimFailed(dbPath, runId, error) {
|
|
|
2116
2157
|
}
|
|
2117
2158
|
|
|
2118
2159
|
// src/cli.ts
|
|
2160
|
+
var stdout = createStdoutWriter(process.stdout, fail);
|
|
2161
|
+
function writeStdout(value) {
|
|
2162
|
+
stdout.write(value);
|
|
2163
|
+
}
|
|
2119
2164
|
async function init(workspace, options) {
|
|
2120
2165
|
const config = createWorkspaceConfig(
|
|
2121
2166
|
workspace,
|
|
@@ -2138,7 +2183,7 @@ async function init(workspace, options) {
|
|
|
2138
2183
|
});
|
|
2139
2184
|
}
|
|
2140
2185
|
db.close();
|
|
2141
|
-
|
|
2186
|
+
writeStdout(
|
|
2142
2187
|
`Workspace: ${config.rootPath}
|
|
2143
2188
|
Database: ${config.dbPath}
|
|
2144
2189
|
Repositories: ${repos.length}
|
|
@@ -2204,7 +2249,7 @@ function createProgram() {
|
|
|
2204
2249
|
repo: opts.repo,
|
|
2205
2250
|
force: Boolean(opts.force)
|
|
2206
2251
|
});
|
|
2207
|
-
|
|
2252
|
+
writeStdout(
|
|
2208
2253
|
`Indexed ${r.indexedCount} repositories, skipped ${r.skippedCount}, ${r.fileCount} files, ${r.diagnosticCount} diagnostics
|
|
2209
2254
|
`
|
|
2210
2255
|
);
|
|
@@ -2214,7 +2259,7 @@ function createProgram() {
|
|
|
2214
2259
|
(opts) => void withWorkspace(opts.workspace, (db, workspaceId) => {
|
|
2215
2260
|
const r = linkWorkspace(db, workspaceId);
|
|
2216
2261
|
const upgradeWarnings = linkUpgradeWarnings(db);
|
|
2217
|
-
|
|
2262
|
+
writeStdout(
|
|
2218
2263
|
`${upgradeWarnings.length ? `Warnings: ${upgradeWarnings.map((item) => String(item.code)).join(", ")}. Run service-flow doctor --strict for remediation.
|
|
2219
2264
|
` : ""}Linked ${r.edgeCount} edges: ${r.remoteResolvedCount} remote operation calls resolved, ${r.localResolvedCount} local operation calls resolved, ${r.unresolvedCount} unresolved operation calls, ${r.ambiguousCount} ambiguous operation calls, ${r.dynamicCount} dynamic operation calls, ${r.terminalCount} terminal call edges, ${r.dependencyResolvedCount} dependency resolved, ${r.dependencyAmbiguousCount} dependency ambiguous, ${r.implementationResolvedCount} implementation resolved, ${r.implementationAmbiguousCount} implementation ambiguous, ${r.implementationUnresolvedCount} implementation unresolved
|
|
2220
2265
|
`
|
|
@@ -2245,7 +2290,7 @@ function createProgram() {
|
|
|
2245
2290
|
maxDynamicCandidates: parsePositiveInteger(opts.maxDynamicCandidates, 5)
|
|
2246
2291
|
}
|
|
2247
2292
|
);
|
|
2248
|
-
|
|
2293
|
+
writeStdout(
|
|
2249
2294
|
opts.format === "json" ? renderTraceJson(result) : opts.format === "mermaid" ? renderMermaid(result) : renderTraceTable(result)
|
|
2250
2295
|
);
|
|
2251
2296
|
}).catch(fail)
|
|
@@ -2254,7 +2299,7 @@ function createProgram() {
|
|
|
2254
2299
|
list.command("repos").option("--workspace <path>").action(
|
|
2255
2300
|
(opts) => void withReadOnlyWorkspace(
|
|
2256
2301
|
opts.workspace,
|
|
2257
|
-
(db, workspaceId) =>
|
|
2302
|
+
(db, workspaceId) => writeStdout(
|
|
2258
2303
|
renderJson(
|
|
2259
2304
|
listRepositories(db, workspaceId).map((r) => ({
|
|
2260
2305
|
name: r.name,
|
|
@@ -2269,35 +2314,35 @@ function createProgram() {
|
|
|
2269
2314
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
2270
2315
|
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
2271
2316
|
if (selection.diagnostic) {
|
|
2272
|
-
|
|
2317
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
2273
2318
|
return;
|
|
2274
2319
|
}
|
|
2275
2320
|
const repo = selection.repo;
|
|
2276
2321
|
const rows = db.prepare(
|
|
2277
2322
|
"SELECT r.name repo,s.service_path servicePath,s.qualified_name qualifiedName FROM cds_services s JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) ORDER BY r.name,s.service_path"
|
|
2278
2323
|
).all(workspaceId, repo?.id, repo?.id);
|
|
2279
|
-
|
|
2324
|
+
writeStdout(renderJson(rows));
|
|
2280
2325
|
}).catch(fail)
|
|
2281
2326
|
);
|
|
2282
2327
|
list.command("operations").option("--workspace <path>").option("--repo <name>").option("--service <path>").action(
|
|
2283
2328
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
2284
2329
|
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
2285
2330
|
if (selection.diagnostic) {
|
|
2286
|
-
|
|
2331
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
2287
2332
|
return;
|
|
2288
2333
|
}
|
|
2289
2334
|
const repo = selection.repo;
|
|
2290
2335
|
const rows = db.prepare(
|
|
2291
2336
|
"SELECT r.name repo,s.service_path servicePath,o.operation_name operation,o.operation_path path FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (? IS NULL OR s.repo_id=?) AND (? IS NULL OR s.service_path=?)"
|
|
2292
2337
|
).all(workspaceId, repo?.id, repo?.id, opts.service, opts.service);
|
|
2293
|
-
|
|
2338
|
+
writeStdout(renderJson(rows));
|
|
2294
2339
|
}).catch(fail)
|
|
2295
2340
|
);
|
|
2296
2341
|
list.command("calls").option("--workspace <path>").option("--repo <name>").option("--operation <name>").action(
|
|
2297
2342
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
2298
2343
|
const selection = opts.repo ? selectRepository(db, opts.repo, workspaceId) : {};
|
|
2299
2344
|
if (selection.diagnostic) {
|
|
2300
|
-
|
|
2345
|
+
writeStdout(renderJson([selection.diagnostic]));
|
|
2301
2346
|
return;
|
|
2302
2347
|
}
|
|
2303
2348
|
const repo = selection.repo;
|
|
@@ -2312,7 +2357,7 @@ function createProgram() {
|
|
|
2312
2357
|
opts.operation ? `/${opts.operation}` : void 0,
|
|
2313
2358
|
opts.operation ? `%${opts.operation}%` : void 0
|
|
2314
2359
|
);
|
|
2315
|
-
|
|
2360
|
+
writeStdout(renderJson(rows));
|
|
2316
2361
|
}).catch(fail)
|
|
2317
2362
|
);
|
|
2318
2363
|
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, []).option("--dynamic-mode <mode>", "strict|candidates|infer", "strict").option("--max-dynamic-candidates <n>", "maximum dynamic candidates to show", "5").action(
|
|
@@ -2338,7 +2383,7 @@ function createProgram() {
|
|
|
2338
2383
|
maxDynamicCandidates: parsePositiveInteger(opts.maxDynamicCandidates, 5)
|
|
2339
2384
|
}
|
|
2340
2385
|
);
|
|
2341
|
-
|
|
2386
|
+
writeStdout(
|
|
2342
2387
|
opts.format === "json" ? renderTraceJson(result) : renderMermaid(result)
|
|
2343
2388
|
);
|
|
2344
2389
|
}).catch(fail)
|
|
@@ -2347,7 +2392,7 @@ function createProgram() {
|
|
|
2347
2392
|
inspect.command("repo").argument("<name>").option("--workspace <path>").action(
|
|
2348
2393
|
(name, opts) => void withReadOnlyWorkspace(opts.workspace, (db, workspaceId) => {
|
|
2349
2394
|
const selection = selectRepository(db, name, workspaceId);
|
|
2350
|
-
|
|
2395
|
+
writeStdout(renderJson(
|
|
2351
2396
|
selection.repo ?? selection.diagnostic ?? { error: "repo not found" }
|
|
2352
2397
|
));
|
|
2353
2398
|
}).catch(fail)
|
|
@@ -2357,20 +2402,20 @@ function createProgram() {
|
|
|
2357
2402
|
const rows = db.prepare(
|
|
2358
2403
|
"SELECT o.* FROM cds_operations o JOIN cds_services s ON s.id=o.service_id JOIN repositories r ON r.id=s.repo_id WHERE r.workspace_id=? AND (o.operation_name=? OR o.operation_path=?)"
|
|
2359
2404
|
).all(workspaceId, selector, selector);
|
|
2360
|
-
|
|
2405
|
+
writeStdout(renderJson(rows));
|
|
2361
2406
|
}).catch(fail)
|
|
2362
2407
|
);
|
|
2363
2408
|
program.command("doctor").option("--workspace <path>").option("--strict").option("--detail").option("--format <format>", "json|table").action(
|
|
2364
2409
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
|
|
2365
2410
|
const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict), { detail: Boolean(opts.detail) });
|
|
2366
|
-
|
|
2411
|
+
writeStdout(renderDoctorDiagnostics(allDiagnostics, opts.format));
|
|
2367
2412
|
}).catch(fail)
|
|
2368
2413
|
);
|
|
2369
2414
|
program.command("clean").option("--workspace <path>").option("--db-only").action(
|
|
2370
2415
|
(opts) => void (async () => {
|
|
2371
2416
|
const config = await loadWorkspaceConfig(opts.workspace);
|
|
2372
2417
|
await cleanWorkspaceState(config, Boolean(opts.dbOnly));
|
|
2373
|
-
|
|
2418
|
+
writeStdout("Cleaned service-flow state\n");
|
|
2374
2419
|
})().catch(fail)
|
|
2375
2420
|
);
|
|
2376
2421
|
return program;
|