@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/dist/cli.js CHANGED
@@ -29,14 +29,13 @@ import {
29
29
  parsePackageJson,
30
30
  parseServiceBindings,
31
31
  parseVars,
32
- projectBounded,
32
+ projectBoundedInOrder,
33
33
  reposByName,
34
34
  selectorRepoAmbiguousDiagnostic,
35
- stableProjectionValue,
36
35
  trace,
37
36
  upsertRepository,
38
37
  upsertWorkspace
39
- } from "./chunk-LFH7C46B.js";
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.53",
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 = projectBounded(child.map(boundDoctorValue), compareDiagnostic);
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[0];
1860
- return `${String(first.sourceFile ?? "")}:${String(first.sourceLine ?? "")}`;
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
- process.stdout.write(
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
- process.stdout.write(
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
- process.stdout.write(
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
- process.stdout.write(
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) => process.stdout.write(
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
- process.stdout.write(renderJson([selection.diagnostic]));
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
- process.stdout.write(renderJson(rows));
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
- process.stdout.write(renderJson([selection.diagnostic]));
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
- process.stdout.write(renderJson(rows));
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
- process.stdout.write(renderJson([selection.diagnostic]));
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
- process.stdout.write(renderJson(rows));
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
- process.stdout.write(
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
- process.stdout.write(renderJson(
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
- process.stdout.write(renderJson(rows));
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
- process.stdout.write(renderDoctorDiagnostics(allDiagnostics, opts.format));
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
- process.stdout.write("Cleaned service-flow state\n");
2418
+ writeStdout("Cleaned service-flow state\n");
2374
2419
  })().catch(fail)
2375
2420
  );
2376
2421
  return program;