@saptools/service-flow 0.1.44 → 0.1.46
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 +9 -1
- package/dist/{chunk-UKNPHTUS.js → chunk-EGY2A4AT.js} +1853 -1462
- package/dist/chunk-EGY2A4AT.js.map +1 -0
- package/dist/cli.js +101 -31
- 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 +67 -13
- package/src/cli.ts +10 -2
- package/src/index.ts +2 -0
- package/src/linker/cross-repo-linker.ts +27 -3
- package/src/linker/odata-path-normalizer.ts +4 -1
- package/src/output/table-output.ts +16 -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 +223 -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
|
@@ -3,17 +3,19 @@ import {
|
|
|
3
3
|
classifyODataPathIntent,
|
|
4
4
|
containsSupportedOutboundCall,
|
|
5
5
|
discoverRepositories,
|
|
6
|
+
implementationHintSuggestions,
|
|
6
7
|
linkWorkspace,
|
|
7
8
|
normalizeODataOperationInvocationPath,
|
|
8
9
|
normalizePath,
|
|
9
10
|
parseCdsFile,
|
|
10
11
|
parseDecorators,
|
|
11
12
|
parseHandlerRegistrations,
|
|
13
|
+
parseImplementationHint,
|
|
12
14
|
parseOutboundCalls,
|
|
13
15
|
parsePackageJson,
|
|
14
16
|
parseServiceBindings,
|
|
15
17
|
trace
|
|
16
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-EGY2A4AT.js";
|
|
17
19
|
|
|
18
20
|
// src/cli.ts
|
|
19
21
|
import { Command } from "commander";
|
|
@@ -209,7 +211,7 @@ function migrate(db) {
|
|
|
209
211
|
// package.json
|
|
210
212
|
var package_default = {
|
|
211
213
|
name: "@saptools/service-flow",
|
|
212
|
-
version: "0.1.
|
|
214
|
+
version: "0.1.46",
|
|
213
215
|
description: "Trace SAP CAP service-to-service flows across multi-repository workspaces with runtime-aware graph resolution",
|
|
214
216
|
type: "module",
|
|
215
217
|
publishConfig: {
|
|
@@ -814,6 +816,10 @@ function argumentEvidence(args, source) {
|
|
|
814
816
|
}
|
|
815
817
|
return { kind: "object_literal", properties };
|
|
816
818
|
}
|
|
819
|
+
if (ts.isArrayLiteralExpression(arg)) {
|
|
820
|
+
const elements = arg.elements.flatMap((element, index) => ts.isIdentifier(element) ? [{ index, kind: "identifier", name: element.text }] : []);
|
|
821
|
+
return { kind: "array_literal", elements };
|
|
822
|
+
}
|
|
817
823
|
return { kind: "unsupported", expression: arg.getText(source) };
|
|
818
824
|
});
|
|
819
825
|
}
|
|
@@ -860,15 +866,21 @@ function parameterPropertyAliases(fn, source) {
|
|
|
860
866
|
function parameterBindings(params) {
|
|
861
867
|
return params.flatMap((param, index) => {
|
|
862
868
|
if (ts.isIdentifier(param.name)) return [{ index, kind: "identifier", name: param.name.text }];
|
|
863
|
-
if (
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
869
|
+
if (ts.isObjectBindingPattern(param.name)) {
|
|
870
|
+
const properties = param.name.elements.flatMap((element) => {
|
|
871
|
+
if (element.dotDotDotToken || ts.isObjectBindingPattern(element.name) || ts.isArrayBindingPattern(element.name)) return [];
|
|
872
|
+
const property = element.propertyName ? nameOf(element.propertyName) : nameOf(element.name);
|
|
873
|
+
if (!property) return [];
|
|
874
|
+
const local = bindingLocalName(element.name, element.initializer);
|
|
875
|
+
return local ? [{ property, local }] : [];
|
|
876
|
+
});
|
|
877
|
+
return properties.length > 0 ? [{ index, kind: "object_pattern", properties }] : [];
|
|
878
|
+
}
|
|
879
|
+
if (ts.isArrayBindingPattern(param.name)) {
|
|
880
|
+
const elements = param.name.elements.flatMap((element, elementIndex) => ts.isBindingElement(element) && !element.dotDotDotToken && ts.isIdentifier(element.name) ? [{ index: elementIndex, local: element.name.text }] : []);
|
|
881
|
+
return elements.length > 0 ? [{ index, kind: "array_pattern", elements }] : [];
|
|
882
|
+
}
|
|
883
|
+
return [];
|
|
872
884
|
});
|
|
873
885
|
}
|
|
874
886
|
async function parseExecutableSymbols(repoPath, filePath) {
|
|
@@ -1295,7 +1307,7 @@ async function indexWorkspace(db, workspaceId, options) {
|
|
|
1295
1307
|
function linkUpgradeWarnings(db) {
|
|
1296
1308
|
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
1309
|
}
|
|
1298
|
-
function doctorDiagnostics(db, strict) {
|
|
1310
|
+
function doctorDiagnostics(db, strict, options = {}) {
|
|
1299
1311
|
const diagnostics = db.prepare("SELECT severity,code,message,source_file sourceFile,source_line sourceLine FROM diagnostics ORDER BY id").all();
|
|
1300
1312
|
return [
|
|
1301
1313
|
...diagnostics,
|
|
@@ -1303,7 +1315,7 @@ function doctorDiagnostics(db, strict) {
|
|
|
1303
1315
|
...localServiceDiagnostics(db, strict),
|
|
1304
1316
|
...schemaDriftDiagnostics(db, strict),
|
|
1305
1317
|
...analyzerVersionDiagnostics(db, strict),
|
|
1306
|
-
...parserQualityDiagnostics(db, strict)
|
|
1318
|
+
...parserQualityDiagnostics(db, strict, options)
|
|
1307
1319
|
];
|
|
1308
1320
|
}
|
|
1309
1321
|
function healthDiagnostics(db, strict) {
|
|
@@ -1380,7 +1392,7 @@ function localServiceDiagnostics(db, strict) {
|
|
|
1380
1392
|
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
1393
|
return out;
|
|
1382
1394
|
}
|
|
1383
|
-
function parserQualityDiagnostics(db, strict) {
|
|
1395
|
+
function parserQualityDiagnostics(db, strict, options) {
|
|
1384
1396
|
if (!strict) return [];
|
|
1385
1397
|
const symbol = symbolCallQuality(db);
|
|
1386
1398
|
const dbq = dbQueryQuality(db);
|
|
@@ -1389,7 +1401,7 @@ function parserQualityDiagnostics(db, strict) {
|
|
|
1389
1401
|
identityAliasBindingQuality(db),
|
|
1390
1402
|
remoteActionNoBindingQuality(db),
|
|
1391
1403
|
contextualImplementationQuality(db),
|
|
1392
|
-
implementationCandidateQuality(db),
|
|
1404
|
+
implementationCandidateQuality(db, Boolean(options.detail)),
|
|
1393
1405
|
classInstanceNoiseQuality(db),
|
|
1394
1406
|
contextualBindingPropagationQuality(db),
|
|
1395
1407
|
nestedThisReceiverQuality(db),
|
|
@@ -1452,19 +1464,26 @@ function graphDynamicFlagQuality(db) {
|
|
|
1452
1464
|
const row = db.prepare("SELECT COUNT(*) count FROM graph_edges WHERE status='terminal' AND is_dynamic=1").get();
|
|
1453
1465
|
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
1466
|
}
|
|
1455
|
-
function implementationCandidateQuality(db) {
|
|
1456
|
-
const categories = [...implementationEdgeCategories(db), missingParameterMetadataCategory(db)].filter((item) => item.count > 0);
|
|
1467
|
+
function implementationCandidateQuality(db, detail) {
|
|
1468
|
+
const categories = [...implementationEdgeCategories(db, detail), missingParameterMetadataCategory(db, detail), dynamicWrapperCategory(db, detail)].filter((item) => item.count > 0);
|
|
1457
1469
|
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 };
|
|
1470
|
+
return { severity: total > 0 ? "warning" : "info", code: "strict_implementation_candidate_quality", message: "Implementation candidate ambiguity and rejection aggregate", total, summary: implementationSummary(categories), categories };
|
|
1459
1471
|
}
|
|
1460
|
-
function implementationEdgeCategories(db) {
|
|
1472
|
+
function implementationEdgeCategories(db, detail) {
|
|
1461
1473
|
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
1474
|
FROM graph_edges e JOIN cds_operations o ON o.id=CAST(e.from_id AS INTEGER)
|
|
1463
1475
|
JOIN cds_services s ON s.id=o.service_id LEFT JOIN cds_operations base ON base.id=o.base_operation_id
|
|
1464
1476
|
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
1477
|
const grouped = /* @__PURE__ */ new Map();
|
|
1466
1478
|
for (const row of rows) addImplementationCategory(grouped, row);
|
|
1467
|
-
return [...grouped.values()].map(({ servicePaths, ...item }) => ({
|
|
1479
|
+
return [...grouped.values()].map(({ servicePaths, ...item }) => ({
|
|
1480
|
+
...item,
|
|
1481
|
+
servicePathPattern: pathPattern(servicePaths),
|
|
1482
|
+
suggestedAction: categoryAction(String(item.category)),
|
|
1483
|
+
suggestedHints: suggestedHints(item.examples),
|
|
1484
|
+
examples: item.examples.slice(0, 3),
|
|
1485
|
+
expandedExamples: detail ? item.examples : void 0
|
|
1486
|
+
}));
|
|
1468
1487
|
}
|
|
1469
1488
|
function addImplementationCategory(grouped, row) {
|
|
1470
1489
|
const evidence = parseObject(row.evidenceJson);
|
|
@@ -1474,21 +1493,57 @@ function addImplementationCategory(grouped, row) {
|
|
|
1474
1493
|
const baseOperation = String(row.baseOperation ?? row.operationName ?? evidence.operationName ?? "unknown");
|
|
1475
1494
|
const key = [category, baseOperation, reason, family].join("\0");
|
|
1476
1495
|
const current = grouped.get(key) ?? { category, baseOperation, reason, candidateFamily: family, count: 0, servicePaths: [], examples: [] };
|
|
1496
|
+
const hintSuggestions = implementationSuggestions(evidence);
|
|
1477
1497
|
current.count += 1;
|
|
1478
1498
|
current.servicePaths.push(String(row.servicePath ?? evidence.servicePath ?? ""));
|
|
1479
|
-
current.examples.push({ servicePath: row.servicePath, operation: row.operationName, status: row.status, reason: row.unresolvedReason });
|
|
1499
|
+
current.examples.push({ servicePath: row.servicePath, operation: row.operationName, status: row.status, reason: row.unresolvedReason, implementationHintSuggestions: hintSuggestions });
|
|
1480
1500
|
grouped.set(key, current);
|
|
1481
1501
|
}
|
|
1482
|
-
function
|
|
1502
|
+
function implementationSuggestions(evidence) {
|
|
1503
|
+
const persisted = asRecords(evidence.implementationHintSuggestions);
|
|
1504
|
+
const suggestions = persisted.length ? persisted : implementationHintSuggestions(evidence);
|
|
1505
|
+
return suggestions.length ? suggestions : void 0;
|
|
1506
|
+
}
|
|
1507
|
+
function suggestedHints(examples) {
|
|
1508
|
+
const hints = examples.flatMap((example) => asRecords(example.implementationHintSuggestions).flatMap((suggestion) => typeof suggestion.cli === "string" ? [String(suggestion.cli)] : []));
|
|
1509
|
+
const unique = [...new Set(hints)].slice(0, 3);
|
|
1510
|
+
return unique.length ? unique : void 0;
|
|
1511
|
+
}
|
|
1512
|
+
function missingParameterMetadataCategory(db, detail = false) {
|
|
1483
1513
|
const examples = db.prepare(`SELECT sc.source_file sourceFile,sc.source_line sourceLine,sc.callee_expression calleeExpression
|
|
1484
1514
|
FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
|
|
1485
1515
|
WHERE sc.status='resolved' AND json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')
|
|
1486
1516
|
AND (s.evidence_json IS NULL OR json_extract(s.evidence_json,'$.parameterBindings') IS NULL)
|
|
1487
|
-
ORDER BY sc.source_file,sc.source_line
|
|
1517
|
+
ORDER BY sc.source_file,sc.source_line`).all();
|
|
1488
1518
|
const row = db.prepare(`SELECT COUNT(*) count FROM symbol_calls sc JOIN symbols s ON s.id=sc.callee_symbol_id
|
|
1489
1519
|
WHERE sc.status='resolved' AND json_extract(sc.evidence_json,'$.callArguments[0].kind') IN ('identifier','object_literal')
|
|
1490
1520
|
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 };
|
|
1521
|
+
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 };
|
|
1522
|
+
}
|
|
1523
|
+
function dynamicWrapperCategory(db, detail) {
|
|
1524
|
+
const rows = db.prepare(`SELECT source_file sourceFile,source_line sourceLine,
|
|
1525
|
+
json_extract(evidence_json,'$.receiver') receiverName,
|
|
1526
|
+
COALESCE(json_extract(evidence_json,'$.missingPathIdentifier'),json_extract(evidence_json,'$.operationPathExpression')) pathIdentifier
|
|
1527
|
+
FROM outbound_calls WHERE call_type='remote_action' AND unresolved_reason='dynamic_operation_path_identifier'
|
|
1528
|
+
ORDER BY source_file,source_line`).all();
|
|
1529
|
+
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 };
|
|
1530
|
+
}
|
|
1531
|
+
function implementationSummary(categories) {
|
|
1532
|
+
const grouped = /* @__PURE__ */ new Map();
|
|
1533
|
+
for (const category of categories) {
|
|
1534
|
+
const key = [category.category, category.candidateFamily, category.reason].join("\0");
|
|
1535
|
+
const current = grouped.get(key) ?? { category: category.category, candidateFamily: category.candidateFamily, reason: category.reason, severity: "warning", suggestedAction: category.suggestedAction, count: 0 };
|
|
1536
|
+
current.count += category.count;
|
|
1537
|
+
grouped.set(key, current);
|
|
1538
|
+
}
|
|
1539
|
+
return [...grouped.values()].sort((left, right) => String(left.category).localeCompare(String(right.category)) || String(left.candidateFamily).localeCompare(String(right.candidateFamily)));
|
|
1540
|
+
}
|
|
1541
|
+
function categoryAction(category) {
|
|
1542
|
+
if (category === "duplicate_package_name_candidates") return "Use scoped --implementation-hint fields to select one repository for each ambiguous hop.";
|
|
1543
|
+
if (category === "missing_strong_ownership_evidence") return "Add an explicit package dependency, local service-path ownership, or registration ownership evidence.";
|
|
1544
|
+
if (category === "missing_parameter_metadata") return "Export a statically analyzable helper with named or destructured parameters.";
|
|
1545
|
+
if (category === "dynamic_wrapper_paths") return "Pass a literal path or provide the reported runtime identifier with --var key=value.";
|
|
1546
|
+
return "Inspect the capped examples and add stronger implementation ownership evidence.";
|
|
1492
1547
|
}
|
|
1493
1548
|
function implementationCategory(row, evidence) {
|
|
1494
1549
|
const reasons = JSON.stringify([evidence.ambiguityReasons, evidence.candidateFamilies, evidence.candidates, row.unresolvedReason]);
|
|
@@ -1670,11 +1725,24 @@ function renderTraceTable(result) {
|
|
|
1670
1725
|
const lines = ["Step Type From To Evidence"];
|
|
1671
1726
|
for (const e of result.edges) {
|
|
1672
1727
|
lines.push(`${String(e.step).padEnd(5)} ${e.type.padEnd(20)} ${e.from.slice(0, 34).padEnd(35)} ${e.to.slice(0, 35).padEnd(36)} ${location(e.evidence)}`);
|
|
1728
|
+
const hint = firstHint(e.evidence);
|
|
1729
|
+
if (e.unresolvedReason && hint) lines.push(` try ${hint}`);
|
|
1673
1730
|
}
|
|
1674
|
-
if (result.diagnostics.length > 0) lines.push("", "Diagnostics:", ...result.diagnostics.
|
|
1731
|
+
if (result.diagnostics.length > 0) lines.push("", "Diagnostics:", ...result.diagnostics.flatMap(diagnosticLines));
|
|
1675
1732
|
return `${lines.join("\n")}
|
|
1676
1733
|
`;
|
|
1677
1734
|
}
|
|
1735
|
+
function diagnosticLines(diagnostic) {
|
|
1736
|
+
const first = `${String(diagnostic.severity ?? "info")} ${String(diagnostic.code ?? "diagnostic")} ${String(diagnostic.message ?? "")}`;
|
|
1737
|
+
const hint = firstHint(diagnostic);
|
|
1738
|
+
return hint ? [first, ` try ${hint}`] : [first];
|
|
1739
|
+
}
|
|
1740
|
+
function firstHint(evidence) {
|
|
1741
|
+
const suggestions = evidence.implementationHintSuggestions;
|
|
1742
|
+
if (!Array.isArray(suggestions)) return void 0;
|
|
1743
|
+
const first = suggestions.find((item) => Boolean(item) && typeof item === "object");
|
|
1744
|
+
return typeof first?.cli === "string" ? first.cli : void 0;
|
|
1745
|
+
}
|
|
1678
1746
|
|
|
1679
1747
|
// src/output/json-output.ts
|
|
1680
1748
|
function renderJson(value) {
|
|
@@ -1788,7 +1856,7 @@ function createProgram() {
|
|
|
1788
1856
|
);
|
|
1789
1857
|
}).catch(fail)
|
|
1790
1858
|
);
|
|
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(
|
|
1859
|
+
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
1860
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
|
|
1793
1861
|
const result = trace(
|
|
1794
1862
|
db,
|
|
@@ -1805,7 +1873,8 @@ function createProgram() {
|
|
|
1805
1873
|
includeExternal: Boolean(opts.includeExternal),
|
|
1806
1874
|
includeDb: Boolean(opts.includeDb),
|
|
1807
1875
|
includeAsync: Boolean(opts.includeAsync),
|
|
1808
|
-
implementationRepo: opts.implementationRepo
|
|
1876
|
+
implementationRepo: opts.implementationRepo,
|
|
1877
|
+
implementationHints: opts.implementationHint.map(parseImplementationHint)
|
|
1809
1878
|
}
|
|
1810
1879
|
);
|
|
1811
1880
|
process.stdout.write(
|
|
@@ -1874,7 +1943,7 @@ function createProgram() {
|
|
|
1874
1943
|
process.stdout.write(renderJson(rows));
|
|
1875
1944
|
}).catch(fail)
|
|
1876
1945
|
);
|
|
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(
|
|
1946
|
+
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
1947
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
|
|
1879
1948
|
const result = trace(
|
|
1880
1949
|
db,
|
|
@@ -1890,7 +1959,8 @@ function createProgram() {
|
|
|
1890
1959
|
includeDb: true,
|
|
1891
1960
|
includeExternal: true,
|
|
1892
1961
|
vars: parseVars(opts.var),
|
|
1893
|
-
implementationRepo: opts.implementationRepo
|
|
1962
|
+
implementationRepo: opts.implementationRepo,
|
|
1963
|
+
implementationHints: opts.implementationHint.map(parseImplementationHint)
|
|
1894
1964
|
}
|
|
1895
1965
|
);
|
|
1896
1966
|
process.stdout.write(
|
|
@@ -1915,9 +1985,9 @@ function createProgram() {
|
|
|
1915
1985
|
process.stdout.write(renderJson(rows));
|
|
1916
1986
|
}).catch(fail)
|
|
1917
1987
|
);
|
|
1918
|
-
program.command("doctor").option("--workspace <path>").option("--strict").action(
|
|
1988
|
+
program.command("doctor").option("--workspace <path>").option("--strict").option("--detail").action(
|
|
1919
1989
|
(opts) => void withReadOnlyWorkspace(opts.workspace, (db) => {
|
|
1920
|
-
const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict));
|
|
1990
|
+
const allDiagnostics = doctorDiagnostics(db, Boolean(opts.strict), { detail: Boolean(opts.detail) });
|
|
1921
1991
|
process.stdout.write(
|
|
1922
1992
|
allDiagnostics.length ? renderJson(allDiagnostics) : `${pc.green("No diagnostics recorded")}
|
|
1923
1993
|
`
|